@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.
- package/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- 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;
|