@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,528 @@
1
+ // The worksheet-body reader: one `xl/worksheets/sheetN.xml` in, its cells/columns/rows/print-setup
2
+ // applied onto a {@link Worksheet}. It is a single streaming pass driving a handful of accumulators
3
+ // (the cell being read, shared-formula masters, an autofilter draft, the current page-break axis) so
4
+ // each element commits its state as it closes. Style indices resolve through the parsed style table.
5
+ import { decodeRange } from "../../core/address.js";
6
+ import { isCustomFilterOperator, } from "../../core/autofilter.js";
7
+ import { INTERNAL } from "../../core/internal.js";
8
+ import { SHEET_PROTECTION_FLAGS, } from "../../core/protection.js";
9
+ import { assignStyleFacets } from "../../core/style.js";
10
+ import { boolPresent, boolStrict, boolTristate, localName, parseXml, } from "../../xml/xml-read.js";
11
+ import { CellAccumulator } from "./cell-accumulator.js";
12
+ import { parseColor } from "./styles.js";
13
+ const MARGIN_SIDES = ['left', 'right', 'top', 'bottom', 'header', 'footer'];
14
+ // Worksheet elements that commit on their close: a formatted-but-empty `<c/>` and a criteria-free
15
+ // self-closing `<autoFilter/>` are expanded to open+close so each finalises once in onClose. The
16
+ // text-bearing `<f/>`/`<v/>`/`<t/>` are deliberately excluded — an empty one must not commit.
17
+ const WORKSHEET_EMPTY_CLOSES = new Set(['c', 'autoFilter']);
18
+ // Fold a filter column's accumulated `<filters>` or `<customFilters>` state into one criteria value,
19
+ // or null when it carried nothing filterable. A `<filters>` block with no values and no blank flag,
20
+ // or a `<customFilters>` with no predicates, is a no-op that would round-trip as noise, so it drops.
21
+ function pendingFilterCriteria(values, blank, predicates, and) {
22
+ if (values !== null && (values.length > 0 || blank)) {
23
+ return { kind: 'values', values, blank };
24
+ }
25
+ if (predicates !== null && predicates.length > 0) {
26
+ return { kind: 'custom', and, predicates: predicates.slice(0, 2) };
27
+ }
28
+ return null;
29
+ }
30
+ // Read a <sheetProtection> element back into a SheetProtection — the deserialization mirror of the
31
+ // writer. `sheet="0"` (or "false") means the element records an *un*protected sheet, so nothing is
32
+ // restored. Each flag attribute is the INVERSE of the author's allow-flag ("1" forbids, "0" permits),
33
+ // and only attributes actually present are carried, so an omitted (default-valued) flag stays absent —
34
+ // exactly what the writer emitted. A password credential is preserved verbatim in its agile form
35
+ // (algorithm, hash, salt, spin count); there is no plaintext password to recover, so it is not re-hashed.
36
+ function parseSheetProtection(attrs) {
37
+ if (attrs.sheet === '0' || attrs.sheet === 'false')
38
+ return undefined;
39
+ const flags = {};
40
+ for (const { key } of SHEET_PROTECTION_FLAGS) {
41
+ const raw = attrs[key];
42
+ if (raw !== undefined)
43
+ flags[key] = !boolStrict(raw);
44
+ }
45
+ const { algorithmName, hashValue, saltValue, spinCount } = attrs;
46
+ if (algorithmName !== undefined &&
47
+ hashValue !== undefined &&
48
+ saltValue !== undefined &&
49
+ spinCount !== undefined) {
50
+ const spin = Number(spinCount);
51
+ if (Number.isFinite(spin)) {
52
+ const credential = {
53
+ algorithmName,
54
+ hashValue,
55
+ saltValue,
56
+ spinCount: spin,
57
+ };
58
+ return { flags, credential };
59
+ }
60
+ }
61
+ return { flags };
62
+ }
63
+ // Autofilter accumulation. The sheet `<autoFilter ref>` seeds a draft; each `<filterColumn colId>`
64
+ // opens a column whose criteria (`<filters>` values or `<customFilters>` predicates) stream into the
65
+ // draft until `</filterColumn>`, and `</autoFilter>` commits the whole thing to the sheet.
66
+ class AutoFilterAccumulator {
67
+ #ref = null;
68
+ #columns = [];
69
+ #colId = -1;
70
+ #values = null;
71
+ #blank = false;
72
+ #predicates = null;
73
+ #and = false;
74
+ // Seed a draft from the range. (A table's own `<autoFilter>` also matches, but table sheets route
75
+ // through parseTable, so this only ever sees the sheet-level one.)
76
+ begin(attrs) {
77
+ this.#ref = attrs.ref !== undefined && attrs.ref !== '' ? attrs.ref : null;
78
+ this.#columns = [];
79
+ }
80
+ // Open a criteria block for one column, offset `colId` from the range's left edge. Reset the
81
+ // per-column accumulators; whichever child (`<filters>`/`<customFilters>`) opens fills one.
82
+ beginColumn(attrs) {
83
+ this.#colId = attrs.colId !== undefined ? Number(attrs.colId) : -1;
84
+ this.#values = null;
85
+ this.#blank = false;
86
+ this.#predicates = null;
87
+ this.#and = false;
88
+ }
89
+ beginValues(attrs) {
90
+ this.#values = [];
91
+ // `blank` defaults off when absent, so presence is required first — then it reads as an
92
+ // on-when-present flag. (`boolPresent` alone would treat the absent attribute as on.)
93
+ this.#blank = attrs.blank !== undefined && boolPresent(attrs.blank);
94
+ }
95
+ addValue(attrs) {
96
+ if (this.#values !== null && attrs.val !== undefined)
97
+ this.#values.push(attrs.val);
98
+ }
99
+ beginCustom(attrs) {
100
+ this.#predicates = [];
101
+ this.#and = attrs.and !== undefined && boolPresent(attrs.and);
102
+ }
103
+ // The operator attribute defaults to `equal` when absent (per CT_CustomFilter); an operand is
104
+ // likewise optional. An unrecognised operator drops the predicate rather than guessing.
105
+ addCustom(attrs) {
106
+ if (this.#predicates === null)
107
+ return;
108
+ const operator = attrs.operator ?? 'equal';
109
+ if (isCustomFilterOperator(operator)) {
110
+ this.#predicates.push({ operator, val: attrs.val ?? '' });
111
+ }
112
+ }
113
+ // Assemble this column's criteria from whichever accumulator filled. A column whose colId is
114
+ // negative, or whose criteria are empty (no values, no blank, no predicates), carries nothing
115
+ // filterable and is dropped so a re-write stays clean — load-repair, not authoring.
116
+ endColumn() {
117
+ const criteria = pendingFilterCriteria(this.#values, this.#blank, this.#predicates, this.#and);
118
+ if (this.#colId >= 0 && criteria !== null) {
119
+ this.#columns.push({ colId: this.#colId, criteria });
120
+ }
121
+ }
122
+ // Commit the accumulated autofilter to the sheet. Runs on `</autoFilter>` — including the synthesized
123
+ // close of a criteria-free self-closing `<autoFilter/>`. Columns whose colId falls outside the range
124
+ // are dropped here so the strict setter never trips on hostile input.
125
+ commit(sheet) {
126
+ if (this.#ref === null)
127
+ return;
128
+ try {
129
+ const { left, right } = decodeRange(this.#ref);
130
+ const width = left !== undefined && right !== undefined ? right - left + 1 : 0;
131
+ sheet.autoFilter = { ref: this.#ref, columns: this.#columns.filter((c) => c.colId < width) };
132
+ }
133
+ catch {
134
+ // unbounded or malformed autofilter range in the source file — ignore it
135
+ }
136
+ this.#ref = null;
137
+ this.#columns = [];
138
+ }
139
+ }
140
+ // Page-break accumulation. `<brk>` elements appear under both `<rowBreaks>` and `<colBreaks>`; a break
141
+ // container's open points the accumulator at that axis's list (null outside any container), so a
142
+ // `<brk>` lands on the right axis, and the matching close clears it. A self-closing
143
+ // `<rowBreaks/>`/`<colBreaks/>` fires no close, so a new open simply reassigns the target.
144
+ class PageBreakAccumulator {
145
+ #target = null;
146
+ begin(target) {
147
+ this.#target = target;
148
+ }
149
+ end() {
150
+ this.#target = null;
151
+ }
152
+ // `id` is the row/column the layout splits before; a non-positive or non-integer id is hostile input
153
+ // and dropped rather than trusted. A `<brk>` outside any break container has no axis and is ignored.
154
+ add(attrs) {
155
+ if (this.#target === null)
156
+ return;
157
+ const id = Number(attrs.id);
158
+ if (!Number.isInteger(id) || id < 1)
159
+ return;
160
+ const brk = { id };
161
+ const max = Number(attrs.max);
162
+ if (Number.isInteger(max) && max >= 0)
163
+ brk.max = max;
164
+ if (boolStrict(attrs.man))
165
+ brk.man = true;
166
+ this.#target.push(brk);
167
+ }
168
+ }
169
+ export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
170
+ // The one `<c>` currently being read: its address/type/style, formula, value, inline text, rich
171
+ // runs, and the sheet-spanning shared-formula master map. Each `<c>` resets it and commits it.
172
+ const cell = new CellAccumulator();
173
+ let inInlineString = false;
174
+ let capture = false;
175
+ let text = '';
176
+ // A row with customFormat="1" supplies a default style for its cells that carry no `s`.
177
+ let rowStyle = -1;
178
+ let rowCustomFormat = false;
179
+ const autoFilter = new AutoFilterAccumulator();
180
+ const pageBreaks = new PageBreakAccumulator();
181
+ // A column's `style` is the default for its cells that carry no style of their own; this
182
+ // maps a column index to that style index so a bare cell can inherit it (as Excel does,
183
+ // without stamping every cell). Columns are parsed before any cell references them.
184
+ const columnStyle = new Map();
185
+ // Commit the cell held in the accumulator, resolving its style from its own `s`, then its row's
186
+ // (when customFormat), then its column's default — the order Excel applies. Runs on `</c>` close,
187
+ // including the synthesized close of a self-closing `<c/>` formatted-but-empty cell.
188
+ const finalizeCellFromState = () => {
189
+ const styleIndex = cell.styleIndex >= 0
190
+ ? cell.styleIndex
191
+ : rowCustomFormat && rowStyle >= 0
192
+ ? rowStyle
193
+ : (columnStyle.get(cell.col) ?? -1);
194
+ const style = styleIndex >= 0 ? xfStyles[styleIndex] : xfStyles[0];
195
+ cell.finalize(sheet, sharedStrings, style);
196
+ };
197
+ parseXml(xml, {
198
+ onOpen(name, attrs, selfClosing) {
199
+ const local = localName(name);
200
+ text = '';
201
+ capture = false;
202
+ switch (local) {
203
+ case 'col':
204
+ applyColumn(sheet, attrs, xfStyles, columnStyle);
205
+ break;
206
+ case 'row':
207
+ applyRow(sheet, attrs);
208
+ rowStyle = attrs.s !== undefined ? Number(attrs.s) : -1;
209
+ rowCustomFormat = boolStrict(attrs.customFormat);
210
+ break;
211
+ case 'c':
212
+ cell.beginCell(attrs);
213
+ break;
214
+ case 'is':
215
+ inInlineString = true;
216
+ cell.beginInlineString();
217
+ break;
218
+ case 'r':
219
+ // A run inside a rich inline string. Its `<rPr>` (if any) and `<t>` follow.
220
+ if (inInlineString)
221
+ cell.runs.beginRun();
222
+ break;
223
+ case 'rPr':
224
+ // The run's formatting bundle; its self-closing children stream into the default branch.
225
+ cell.runs.beginProperties();
226
+ break;
227
+ case 'f':
228
+ capture = true;
229
+ cell.beginFormula(attrs, selfClosing);
230
+ break;
231
+ case 'v':
232
+ case 't':
233
+ capture = true;
234
+ break;
235
+ case 'oddHeader':
236
+ case 'oddFooter':
237
+ case 'evenHeader':
238
+ case 'evenFooter':
239
+ case 'firstHeader':
240
+ case 'firstFooter':
241
+ // A `<headerFooter>` child carries its header/footer definition as text (the `&`-prefixed
242
+ // section/format tokens, e.g. `&C&"Arial"&G`). Capture it verbatim so a round-trip preserves
243
+ // a header image's `&G` picture token and every other formatting directive.
244
+ capture = true;
245
+ break;
246
+ case 'mergeCell':
247
+ // A well-formed file never declares overlapping merges; a corrupt one might. Reject the
248
+ // bad range at the model boundary, but don't let one abort the whole parse — drop it and
249
+ // keep reading the valid geometry.
250
+ if (attrs.ref !== undefined && attrs.ref !== '') {
251
+ try {
252
+ sheet.mergeCells(attrs.ref);
253
+ }
254
+ catch {
255
+ // overlapping/malformed merge in the source file — skip it
256
+ }
257
+ }
258
+ break;
259
+ case 'tabColor':
260
+ case 'outlinePr':
261
+ case 'pane':
262
+ case 'pageSetUpPr':
263
+ case 'printOptions':
264
+ case 'pageMargins':
265
+ case 'pageSetup':
266
+ applySheetProperties(local, attrs, sheet);
267
+ break;
268
+ case 'rowBreaks':
269
+ pageBreaks.begin(sheet.rowBreaks);
270
+ break;
271
+ case 'colBreaks':
272
+ pageBreaks.begin(sheet.columnBreaks);
273
+ break;
274
+ case 'brk':
275
+ pageBreaks.add(attrs);
276
+ break;
277
+ case 'sheetProtection': {
278
+ const protection = parseSheetProtection(attrs);
279
+ if (protection !== undefined)
280
+ sheet[INTERNAL].restoreProtection(protection);
281
+ break;
282
+ }
283
+ case 'autoFilter':
284
+ autoFilter.begin(attrs);
285
+ break;
286
+ case 'filterColumn':
287
+ autoFilter.beginColumn(attrs);
288
+ break;
289
+ case 'filters':
290
+ autoFilter.beginValues(attrs);
291
+ break;
292
+ case 'filter':
293
+ autoFilter.addValue(attrs);
294
+ break;
295
+ case 'customFilters':
296
+ autoFilter.beginCustom(attrs);
297
+ break;
298
+ case 'customFilter':
299
+ autoFilter.addCustom(attrs);
300
+ break;
301
+ default:
302
+ // A run's `<rPr>` child (`<b/>`, `<sz>`, `<color>`, `<rFont>`, …) sets one font facet; it
303
+ // is self-closing, so it is read here on open. Nothing else uses the default branch.
304
+ cell.runs.applyProperty(local, attrs);
305
+ break;
306
+ }
307
+ if (selfClosing && (local === 'f' || local === 'v'))
308
+ capture = false;
309
+ },
310
+ onText(chunk) {
311
+ if (capture)
312
+ text += chunk;
313
+ },
314
+ onClose(name) {
315
+ const local = localName(name);
316
+ switch (local) {
317
+ case 'f':
318
+ cell.setFormula(text);
319
+ break;
320
+ case 'v':
321
+ cell.setValue(text);
322
+ break;
323
+ case 't':
324
+ // A `<t>` inside a run is that run's text; a bare `<t>` directly in the `<is>` is a plain
325
+ // inline string. A run takes precedence — a run is also inside the inline string.
326
+ cell.appendText(text, inInlineString);
327
+ break;
328
+ case 'r':
329
+ cell.runs.endRun();
330
+ break;
331
+ case 'is':
332
+ inInlineString = false;
333
+ break;
334
+ case 'oddHeader':
335
+ case 'oddFooter':
336
+ case 'evenHeader':
337
+ case 'evenFooter':
338
+ case 'firstHeader':
339
+ case 'firstFooter':
340
+ sheet.headerFooter[local] = text;
341
+ break;
342
+ case 'c':
343
+ finalizeCellFromState();
344
+ break;
345
+ case 'row':
346
+ rowStyle = -1;
347
+ rowCustomFormat = false;
348
+ break;
349
+ case 'filterColumn':
350
+ autoFilter.endColumn();
351
+ break;
352
+ case 'autoFilter':
353
+ autoFilter.commit(sheet);
354
+ break;
355
+ case 'rowBreaks':
356
+ case 'colBreaks':
357
+ pageBreaks.end();
358
+ break;
359
+ default:
360
+ break;
361
+ }
362
+ capture = false;
363
+ },
364
+ }, { closeEmptyElements: WORKSHEET_EMPTY_CLOSES });
365
+ }
366
+ // Apply one `<sheetPr>` / `<sheetView>` / print-setup child to the sheet. These are the worksheet's
367
+ // layout and print metadata; grouping them here keeps the cell-reading switch a pure dispatch. Each
368
+ // records only what the source carried, so a file missing a facet leaves it unset and a re-write
369
+ // stays byte-clean. Every element here is self-closing (its state is all in attributes), so it is
370
+ // read on open.
371
+ function applySheetProperties(local, attrs, sheet) {
372
+ switch (local) {
373
+ case 'tabColor':
374
+ // A `<sheetPr>` child.
375
+ sheet.tabColor = parseColor(attrs);
376
+ break;
377
+ case 'outlinePr':
378
+ // A `<sheetPr>` child.
379
+ if (attrs.summaryBelow !== undefined)
380
+ sheet.outline.summaryBelow = boolPresent(attrs.summaryBelow);
381
+ if (attrs.summaryRight !== undefined)
382
+ sheet.outline.summaryRight = boolPresent(attrs.summaryRight);
383
+ break;
384
+ case 'pane':
385
+ // A `<sheetView>` child recording a frozen (or split) pane. Only a frozen pane maps onto the
386
+ // model's view; a source without one leaves `view` empty, so a re-write emits no pane.
387
+ if (attrs.state === 'frozen' || attrs.state === 'frozenSplit') {
388
+ sheet.view.state = 'frozen';
389
+ if (attrs.xSplit !== undefined)
390
+ sheet.view.xSplit = Number(attrs.xSplit);
391
+ if (attrs.ySplit !== undefined)
392
+ sheet.view.ySplit = Number(attrs.ySplit);
393
+ if (attrs.topLeftCell !== undefined)
394
+ sheet.view.topLeftCell = attrs.topLeftCell;
395
+ }
396
+ break;
397
+ case 'pageSetUpPr':
398
+ // The fit-to-page flag, a `<sheetPr>` child. Recorded only when the attribute is present, so a
399
+ // `<pageSetUpPr>` present for other reasons (e.g. `autoPageBreaks`) leaves `fitToPage` unset.
400
+ if (attrs.fitToPage !== undefined)
401
+ sheet.pageSetup.fitToPage = boolPresent(attrs.fitToPage);
402
+ break;
403
+ case 'printOptions':
404
+ applyPrintOptions(sheet.printOptions, attrs);
405
+ break;
406
+ case 'pageMargins':
407
+ applyMargins(sheet.pageMargins, attrs);
408
+ break;
409
+ case 'pageSetup':
410
+ applyPageSetup(sheet.pageSetup, attrs);
411
+ break;
412
+ }
413
+ }
414
+ function applyColumn(sheet, attrs, xfStyles, columnStyle) {
415
+ const min = Number(attrs.min);
416
+ const max = Number(attrs.max);
417
+ if (!Number.isInteger(min) || !Number.isInteger(max) || min < 1)
418
+ return;
419
+ const width = attrs.width !== undefined ? Number(attrs.width) : undefined;
420
+ const hidden = boolStrict(attrs.hidden);
421
+ const styleIndex = attrs.style !== undefined ? Number(attrs.style) : -1;
422
+ // The column's style resolves to the same facet bundle a cell's does; mirror all of it onto the
423
+ // column model so `getColumn(i)` reflects the declared default, not just its number format.
424
+ const style = styleIndex >= 0 ? xfStyles[styleIndex] : undefined;
425
+ for (let index = min; index <= max; index++) {
426
+ const column = sheet.getColumn(index);
427
+ if (width !== undefined && Number.isFinite(width) && attrs.customWidth !== '0')
428
+ column.width = width;
429
+ if (hidden)
430
+ column.hidden = true;
431
+ if (attrs.outlineLevel !== undefined) {
432
+ const level = Number(attrs.outlineLevel);
433
+ if (Number.isInteger(level) && level > 0)
434
+ column.outlineLevel = level;
435
+ }
436
+ if (boolStrict(attrs.collapsed))
437
+ column.collapsed = true;
438
+ if (style !== undefined)
439
+ assignStyleFacets(column, style);
440
+ // Record the column's style so a bare cell in it can inherit the full column format on read.
441
+ if (styleIndex >= 0)
442
+ columnStyle.set(index, styleIndex);
443
+ }
444
+ }
445
+ function applyRow(sheet, attrs) {
446
+ const number = Number(attrs.r);
447
+ if (!Number.isInteger(number) || number < 1)
448
+ return;
449
+ // A `<row>` that states no attribute at all leaves no format record behind — the handle creates
450
+ // one only when something is written through it. That is the right reading: a bare `<row r="5"/>`
451
+ // carries no formatting to round-trip, and fabricating an empty record for it would put row 5 in
452
+ // the used range on the strength of an element that says nothing.
453
+ const row = sheet.getRow(number);
454
+ if (attrs.ht !== undefined && attrs.customHeight !== '0') {
455
+ const height = Number(attrs.ht);
456
+ if (Number.isFinite(height))
457
+ row.height = height;
458
+ }
459
+ if (boolStrict(attrs.hidden))
460
+ row.hidden = true;
461
+ if (attrs.outlineLevel !== undefined) {
462
+ const level = Number(attrs.outlineLevel);
463
+ if (Number.isInteger(level) && level > 0)
464
+ row.outlineLevel = level;
465
+ }
466
+ if (boolStrict(attrs.collapsed))
467
+ row.collapsed = true;
468
+ }
469
+ // Read the `<printOptions>` boolean toggles back onto the model, storing only the ones the source
470
+ // carried so a re-write stays byte-clean. An OOXML boolean is `1`/`true` for on and `0`/`false` for
471
+ // off; a present-but-unrecognised token is dropped rather than coerced.
472
+ function applyPrintOptions(printOptions, attrs) {
473
+ const horizontalCentered = boolTristate(attrs.horizontalCentered);
474
+ if (horizontalCentered !== undefined)
475
+ printOptions.horizontalCentered = horizontalCentered;
476
+ const verticalCentered = boolTristate(attrs.verticalCentered);
477
+ if (verticalCentered !== undefined)
478
+ printOptions.verticalCentered = verticalCentered;
479
+ const headings = boolTristate(attrs.headings);
480
+ if (headings !== undefined)
481
+ printOptions.headings = headings;
482
+ const gridLines = boolTristate(attrs.gridLines);
483
+ if (gridLines !== undefined)
484
+ printOptions.gridLines = gridLines;
485
+ const gridLinesSet = boolTristate(attrs.gridLinesSet);
486
+ if (gridLinesSet !== undefined)
487
+ printOptions.gridLinesSet = gridLinesSet;
488
+ }
489
+ function applyMargins(margins, attrs) {
490
+ for (const side of MARGIN_SIDES) {
491
+ const raw = attrs[side];
492
+ if (raw === undefined)
493
+ continue;
494
+ const value = Number(raw);
495
+ if (Number.isFinite(value))
496
+ margins[side] = value;
497
+ }
498
+ }
499
+ // Read the `<pageSetup>` print-scaling attributes back onto the model, setting only those the
500
+ // source carried so a re-write stays byte-clean. Numeric attributes that fail to parse are
501
+ // dropped rather than stored as NaN; the enumerated ones are trusted verbatim (an unexpected token
502
+ // round-trips harmlessly as an unknown string).
503
+ function applyPageSetup(pageSetup, attrs) {
504
+ const num = (raw) => {
505
+ if (raw === undefined)
506
+ return undefined;
507
+ const value = Number(raw);
508
+ return Number.isFinite(value) ? value : undefined;
509
+ };
510
+ const paperSize = num(attrs.paperSize);
511
+ if (paperSize !== undefined)
512
+ pageSetup.paperSize = paperSize;
513
+ const scale = num(attrs.scale);
514
+ if (scale !== undefined)
515
+ pageSetup.scale = scale;
516
+ const fitToWidth = num(attrs.fitToWidth);
517
+ if (fitToWidth !== undefined)
518
+ pageSetup.fitToWidth = fitToWidth;
519
+ const fitToHeight = num(attrs.fitToHeight);
520
+ if (fitToHeight !== undefined)
521
+ pageSetup.fitToHeight = fitToHeight;
522
+ if (attrs.pageOrder === 'downThenOver' || attrs.pageOrder === 'overThenDown') {
523
+ pageSetup.pageOrder = attrs.pageOrder;
524
+ }
525
+ if (attrs.orientation === 'portrait' || attrs.orientation === 'landscape') {
526
+ pageSetup.orientation = attrs.orientation;
527
+ }
528
+ }
@@ -0,0 +1,32 @@
1
+ import { Workbook, type WorkbookView } from '../../core/workbook.ts';
2
+ import { type WorkbookProtection } from '../../core/workbook-protection.ts';
3
+ import type { WorksheetState } from '../../core/worksheet.ts';
4
+ import { type ReadXlsxOptions } from '../opc/read-options.ts';
5
+ export { parseRelationships, resolveWorkbookPart } from '../opc/read-opc.ts';
6
+ export { DEFAULT_MAX_UNCOMPRESSED, type ReadXlsxOptions } from '../opc/read-options.ts';
7
+ export type { StyleTable, XfStyle } from '../style/xf-style.ts';
8
+ export { parseStyleTable } from './read-styles.ts';
9
+ /**
10
+ * Read a spreadsheet package into a {@link Workbook}.
11
+ *
12
+ * Both OOXML serialisations are accepted: an XML `.xlsx`, and a binary `.xlsb` (BIFF12), which is the
13
+ * same OPC container with binary office-document parts. The two are auto-detected from the package
14
+ * itself rather than from a file extension, so a caller never branches on which form it holds — and
15
+ * the model produced is the same either way. See `../xlsb/read.ts` for what the binary path does not
16
+ * yet decode.
17
+ *
18
+ * @throws {UnsupportedFormatError} if the input is neither — a legacy `.xls` (`.format === 'xls'`) or
19
+ * an unrecognised/non-ZIP blob (`'unknown'`).
20
+ * @throws {XlsbParseError} if a binary `.xlsb` part is malformed.
21
+ * @throws {@link PackageReadError} if the input is a ZIP that cannot be unpacked — a corrupt or
22
+ * truncated archive, or one exceeding the inflate bound (a probable zip bomb).
23
+ */
24
+ export declare function readXlsx(data: Uint8Array, options?: ReadXlsxOptions): Workbook;
25
+ export interface SheetEntry {
26
+ readonly name: string;
27
+ readonly relId: string;
28
+ readonly state?: WorksheetState['state'];
29
+ }
30
+ export declare function parseWorkbookSheets(xml: string): SheetEntry[];
31
+ export declare function parseWorkbookProtection(xml: string): WorkbookProtection | undefined;
32
+ export declare function applyWorkbookView(view: WorkbookView, xml: string): void;