@shbernal/ts-xlsx 2.1.0 → 3.1.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/README.md +4 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +60 -2
- package/dist/core/address.js +40 -24
- package/dist/core/autofilter.d.ts +5 -4
- package/dist/core/autofilter.js +34 -35
- package/dist/core/axis-handle.d.ts +10 -0
- package/dist/core/axis-handle.js +17 -0
- package/dist/core/cell.d.ts +25 -0
- package/dist/core/cell.js +9 -3
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +2 -4
- package/dist/core/column.d.ts +5 -1
- package/dist/core/column.js +34 -35
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +2 -1
- package/dist/core/conditional-formatting-overlay.js +2 -2
- package/dist/core/conditional-formatting.d.ts +14 -6
- package/dist/core/conditional-formatting.js +45 -43
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +2 -1
- package/dist/core/data-validation-overlay.js +4 -4
- package/dist/core/data-validation.d.ts +9 -3
- package/dist/core/data-validation.js +21 -18
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +160 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +18 -2
- package/dist/core/formula.d.ts +26 -0
- package/dist/core/formula.js +20 -4
- package/dist/core/grid-edits.d.ts +2 -3
- package/dist/core/grid-edits.js +69 -70
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/image.d.ts +14 -10
- package/dist/core/image.js +9 -9
- package/dist/core/internal.d.ts +8 -2
- package/dist/core/limits.d.ts +23 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +5 -14
- package/dist/core/merge.js +18 -27
- package/dist/core/page-setup.d.ts +72 -2
- package/dist/core/page-setup.js +34 -8
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +67 -53
- package/dist/core/protection.js +2 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +5 -1
- package/dist/core/row.js +22 -23
- package/dist/core/style.d.ts +134 -75
- package/dist/core/style.js +112 -53
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +28 -10
- package/dist/core/table.js +54 -39
- package/dist/core/theme.js +6 -5
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- package/dist/core/value.d.ts +9 -0
- package/dist/core/value.js +36 -34
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +67 -1
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +23 -2
- package/dist/core/workbook.js +24 -40
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +44 -0
- package/dist/core/worksheet-merges.js +59 -0
- package/dist/core/worksheet-model.d.ts +9 -1
- package/dist/core/worksheet-model.js +24 -9
- package/dist/core/worksheet.d.ts +72 -12
- package/dist/core/worksheet.js +68 -70
- package/dist/customui/ribbon.js +6 -7
- package/dist/entries/core.d.ts +18 -13
- package/dist/entries/core.js +12 -6
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/csv/delimiter.d.ts +9 -0
- package/dist/io/csv/delimiter.js +6 -0
- package/dist/io/csv/read.js +11 -4
- package/dist/io/csv/write.d.ts +9 -2
- package/dist/io/csv/write.js +9 -38
- package/dist/io/opc/inflate.js +15 -1
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +10 -0
- package/dist/io/opc/part-paths.js +16 -1
- package/dist/io/opc/read-opc.d.ts +69 -10
- package/dist/io/opc/read-opc.js +49 -55
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/rels.d.ts +1 -6
- package/dist/io/opc/rels.js +1 -4
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/style/xf-style.d.ts +48 -2
- package/dist/io/style/xf-style.js +14 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +49 -34
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +385 -596
- package/dist/io/xlsb/read-styles.js +50 -83
- package/dist/io/xlsb/read-worksheet.d.ts +20 -1
- package/dist/io/xlsb/read-worksheet.js +73 -89
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +47 -29
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +1 -5
- package/dist/io/xlsb/record-types.js +1 -5
- package/dist/io/xlsx/cell-accumulator.d.ts +9 -2
- package/dist/io/xlsx/cell-accumulator.js +32 -9
- package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
- package/dist/io/xlsx/cell-style-resolution.js +29 -0
- package/dist/io/xlsx/cell-value.d.ts +5 -3
- package/dist/io/xlsx/cell-value.js +13 -15
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-budget.d.ts +26 -0
- package/dist/io/xlsx/column-budget.js +22 -0
- package/dist/io/xlsx/comments.d.ts +6 -2
- package/dist/io/xlsx/comments.js +36 -31
- package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
- package/dist/io/xlsx/conditional-formatting.js +51 -46
- package/dist/io/xlsx/data-validation.d.ts +9 -7
- package/dist/io/xlsx/data-validation.js +38 -35
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +15 -29
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +41 -7
- package/dist/io/xlsx/hyperlinks.js +40 -28
- package/dist/io/xlsx/images.js +24 -25
- package/dist/io/xlsx/namespaces.d.ts +16 -0
- package/dist/io/xlsx/namespaces.js +7 -0
- package/dist/io/xlsx/package-plan.d.ts +23 -3
- package/dist/io/xlsx/package-plan.js +66 -25
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/pivot.js +8 -7
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +252 -0
- package/dist/io/xlsx/read-pivot.js +3 -1
- package/dist/io/xlsx/read-repair.d.ts +26 -0
- package/dist/io/xlsx/read-repair.js +39 -0
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +1 -1
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +50 -37
- package/dist/io/xlsx/read-shared-strings.js +5 -3
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +65 -109
- package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
- package/dist/io/xlsx/read-workbook-xml.js +170 -0
- package/dist/io/xlsx/read-worksheet.d.ts +11 -1
- package/dist/io/xlsx/read-worksheet.js +51 -193
- package/dist/io/xlsx/read.d.ts +5 -13
- package/dist/io/xlsx/read.js +111 -436
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +18 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +128 -0
- package/dist/io/xlsx/row-xml.js +203 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +166 -37
- package/dist/io/xlsx/style-elements.d.ts +43 -0
- package/dist/io/xlsx/style-elements.js +156 -0
- package/dist/io/xlsx/styles.d.ts +12 -3
- package/dist/io/xlsx/styles.js +5 -204
- package/dist/io/xlsx/tables.js +11 -14
- package/dist/io/xlsx/theme-xml.d.ts +26 -4
- package/dist/io/xlsx/theme-xml.js +107 -69
- package/dist/io/xlsx/threaded-comments.d.ts +8 -10
- package/dist/io/xlsx/threaded-comments.js +3 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +84 -57
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +59 -242
- package/dist/io/xlsx/write-stream.d.ts +71 -16
- package/dist/io/xlsx/write-stream.js +79 -41
- package/dist/io/xlsx/write.d.ts +6 -2
- package/dist/io/xlsx/write.js +217 -153
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +28 -0
- package/dist/token-set.js +7 -0
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +32 -0
- package/dist/vba/cfb-format.js +13 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +28 -25
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -57
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +79 -23
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +40 -21
- package/dist/vba/vba-encoding.js +5 -6
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +128 -81
- package/dist/xml/xml-read.js +161 -178
- package/dist/xml/xml-scan.d.ts +78 -0
- package/dist/xml/xml-scan.js +180 -0
- package/dist/xml/xml.d.ts +29 -28
- package/dist/xml/xml.js +24 -24
- package/package.json +13 -9
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ALIGNMENT_FACETS, FILL_PATTERNS_IN_SCHEMA_ORDER, } from '../../core/style.js';
|
|
2
|
+
import { numFmtCodeFor, NO_PRESERVED_STYLE_TABLES, protectionFrom, resolveStyleTable, } from '../style/xf-style.js';
|
|
2
3
|
import { RecordReader } from './primitives.js';
|
|
3
|
-
import { readRecords } from './record-stream.js';
|
|
4
|
+
import { blockTracker, readRecords } from './record-stream.js';
|
|
4
5
|
import { BRT } from './record-types.js';
|
|
5
|
-
const
|
|
6
|
-
[BRT.BeginFmts, 'fmts'],
|
|
7
|
-
[BRT.BeginFonts, 'fonts'],
|
|
8
|
-
[BRT.BeginFills, 'fills'],
|
|
9
|
-
[BRT.BeginBorders, 'borders'],
|
|
10
|
-
[BRT.BeginCellStyleXFs, 'cellStyleXfs'],
|
|
11
|
-
[BRT.BeginCellXFs, 'cellXfs'],
|
|
12
|
-
[BRT.BeginStyles, 'styles'],
|
|
13
|
-
]
|
|
14
|
-
const COLLECTION_ENDS = new Set([
|
|
15
|
-
BRT.EndFmts,
|
|
16
|
-
BRT.EndFonts,
|
|
17
|
-
BRT.EndFills,
|
|
18
|
-
BRT.EndBorders,
|
|
19
|
-
BRT.EndCellStyleXFs,
|
|
20
|
-
BRT.EndCellXFs,
|
|
21
|
-
BRT.EndStyles,
|
|
22
|
-
]);
|
|
6
|
+
const COLLECTIONS = [
|
|
7
|
+
[BRT.BeginFmts, BRT.EndFmts, 'fmts'],
|
|
8
|
+
[BRT.BeginFonts, BRT.EndFonts, 'fonts'],
|
|
9
|
+
[BRT.BeginFills, BRT.EndFills, 'fills'],
|
|
10
|
+
[BRT.BeginBorders, BRT.EndBorders, 'borders'],
|
|
11
|
+
[BRT.BeginCellStyleXFs, BRT.EndCellStyleXFs, 'cellStyleXfs'],
|
|
12
|
+
[BRT.BeginCellXFs, BRT.EndCellXFs, 'cellXfs'],
|
|
13
|
+
[BRT.BeginStyles, BRT.EndStyles, 'styles'],
|
|
14
|
+
];
|
|
23
15
|
export function parseStyleTable(part) {
|
|
24
16
|
if (part === undefined)
|
|
25
|
-
return { cellXfs: [], namedStyles: [] };
|
|
17
|
+
return { cellXfs: [], namedStyles: [], preserved: NO_PRESERVED_STYLE_TABLES };
|
|
26
18
|
const numFmtCodes = new Map();
|
|
27
19
|
const fonts = [];
|
|
28
20
|
const fills = [];
|
|
@@ -30,50 +22,46 @@ export function parseStyleTable(part) {
|
|
|
30
22
|
const namedXfs = [];
|
|
31
23
|
const directXfs = [];
|
|
32
24
|
const labels = [];
|
|
33
|
-
|
|
25
|
+
const blocks = blockTracker(COLLECTIONS);
|
|
34
26
|
for (const record of readRecords(part)) {
|
|
35
|
-
if (
|
|
36
|
-
collection = undefined;
|
|
27
|
+
if (blocks.boundary(record.type))
|
|
37
28
|
continue;
|
|
38
|
-
}
|
|
39
|
-
const started = COLLECTION_STARTS.get(record.type);
|
|
40
|
-
if (started !== undefined) {
|
|
41
|
-
collection = started;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
29
|
const reader = new RecordReader(record.data);
|
|
45
30
|
switch (record.type) {
|
|
46
31
|
case BRT.Fmt:
|
|
47
|
-
if (
|
|
32
|
+
if (blocks.isOpen('fmts'))
|
|
48
33
|
numFmtCodes.set(reader.u16(), reader.wideString());
|
|
49
34
|
break;
|
|
50
35
|
case BRT.Font:
|
|
51
|
-
if (
|
|
36
|
+
if (blocks.isOpen('fonts'))
|
|
52
37
|
fonts.push(readFont(reader));
|
|
53
38
|
break;
|
|
54
39
|
case BRT.Fill:
|
|
55
|
-
if (
|
|
40
|
+
if (blocks.isOpen('fills'))
|
|
56
41
|
fills.push(readFill(reader));
|
|
57
42
|
break;
|
|
58
43
|
case BRT.Border:
|
|
59
|
-
if (
|
|
44
|
+
if (blocks.isOpen('borders'))
|
|
60
45
|
borders.push(readBorder(reader));
|
|
61
46
|
break;
|
|
62
47
|
case BRT.XF:
|
|
63
|
-
if (
|
|
48
|
+
if (blocks.isOpen('cellXfs') || blocks.isOpen('cellStyleXfs')) {
|
|
64
49
|
const deps = { fonts, fills, borders, numFmtCodes };
|
|
65
|
-
(
|
|
50
|
+
(blocks.isOpen('cellXfs') ? directXfs : namedXfs).push(readXf(reader, deps, blocks.isOpen('cellXfs')));
|
|
66
51
|
}
|
|
67
52
|
break;
|
|
68
53
|
case BRT.Style:
|
|
69
|
-
if (
|
|
54
|
+
if (blocks.isOpen('styles'))
|
|
70
55
|
labels.push(readStyleLabel(reader));
|
|
71
56
|
break;
|
|
72
57
|
default:
|
|
73
58
|
break;
|
|
74
59
|
}
|
|
75
60
|
}
|
|
76
|
-
return
|
|
61
|
+
return {
|
|
62
|
+
...resolveStyleTable({ directXfs, namedXfs, labels, fonts }),
|
|
63
|
+
preserved: NO_PRESERVED_STYLE_TABLES,
|
|
64
|
+
};
|
|
77
65
|
}
|
|
78
66
|
function readXf(reader, deps, isDirect) {
|
|
79
67
|
const parent = reader.u16();
|
|
@@ -107,34 +95,33 @@ function readXf(reader, deps, isDirect) {
|
|
|
107
95
|
return draft;
|
|
108
96
|
}
|
|
109
97
|
const NOT_A_CELL_XF = 0xffff;
|
|
98
|
+
const BIFF12_ALIGNMENT = {
|
|
99
|
+
horizontal: ({ flags }) => {
|
|
100
|
+
const value = HORIZONTAL_ALIGNMENTS[flags & 0b111];
|
|
101
|
+
return value === undefined ? {} : { horizontal: value };
|
|
102
|
+
},
|
|
103
|
+
vertical: ({ flags }) => {
|
|
104
|
+
const value = VERTICAL_ALIGNMENTS[(flags >> 3) & 0b111];
|
|
105
|
+
return value === undefined ? {} : { vertical: value };
|
|
106
|
+
},
|
|
107
|
+
textRotation: ({ rotation }) => (rotation === 0 ? {} : { textRotation: rotation }),
|
|
108
|
+
wrapText: ({ flags }) => ((flags & 0x0040) === 0 ? {} : { wrapText: true }),
|
|
109
|
+
indent: ({ indent }) => (indent === 0 ? {} : { indent }),
|
|
110
|
+
shrinkToFit: ({ flags }) => ((flags & 0x0100) === 0 ? {} : { shrinkToFit: true }),
|
|
111
|
+
readingOrder: ({ flags }) => {
|
|
112
|
+
const value = (flags >> 10) & 0b11;
|
|
113
|
+
return value === 0 ? {} : { readingOrder: value };
|
|
114
|
+
},
|
|
115
|
+
};
|
|
110
116
|
function readAlignment(flags, rotation, indent) {
|
|
117
|
+
const bits = { flags, rotation, indent };
|
|
111
118
|
const out = {};
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
out.horizontal = horizontal;
|
|
115
|
-
const vertical = VERTICAL_ALIGNMENTS[(flags >> 3) & 0b111];
|
|
116
|
-
if (vertical !== undefined)
|
|
117
|
-
out.vertical = vertical;
|
|
118
|
-
if (rotation !== 0)
|
|
119
|
-
out.textRotation = rotation;
|
|
120
|
-
if ((flags & 0x0040) !== 0)
|
|
121
|
-
out.wrapText = true;
|
|
122
|
-
if (indent !== 0)
|
|
123
|
-
out.indent = indent;
|
|
124
|
-
if ((flags & 0x0100) !== 0)
|
|
125
|
-
out.shrinkToFit = true;
|
|
126
|
-
const readingOrder = (flags >> 10) & 0b11;
|
|
127
|
-
if (readingOrder !== 0)
|
|
128
|
-
out.readingOrder = readingOrder;
|
|
119
|
+
for (const facet of ALIGNMENT_FACETS)
|
|
120
|
+
Object.assign(out, BIFF12_ALIGNMENT[facet.key](bits));
|
|
129
121
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
130
122
|
}
|
|
131
123
|
function readProtection(flags) {
|
|
132
|
-
|
|
133
|
-
if ((flags & 0x1000) === 0)
|
|
134
|
-
out.locked = false;
|
|
135
|
-
if ((flags & 0x2000) !== 0)
|
|
136
|
-
out.hidden = true;
|
|
137
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
124
|
+
return protectionFrom({ locked: (flags & 0x1000) !== 0, hidden: (flags & 0x2000) !== 0 });
|
|
138
125
|
}
|
|
139
126
|
const HORIZONTAL_ALIGNMENTS = [
|
|
140
127
|
undefined,
|
|
@@ -205,7 +192,8 @@ const UNDERLINE_STYLES = new Map([
|
|
|
205
192
|
[0x22, 'doubleAccounting'],
|
|
206
193
|
]);
|
|
207
194
|
function readFill(reader) {
|
|
208
|
-
const
|
|
195
|
+
const raw = FILL_PATTERNS_IN_SCHEMA_ORDER[reader.u32()];
|
|
196
|
+
const pattern = raw === 'none' ? undefined : raw;
|
|
209
197
|
if (pattern === undefined)
|
|
210
198
|
return undefined;
|
|
211
199
|
const fgColor = notSentinel(reader.color(), AUTOMATIC_FOREGROUND);
|
|
@@ -222,27 +210,6 @@ const AUTOMATIC_BACKGROUND = 65;
|
|
|
222
210
|
function notSentinel(color, sentinel) {
|
|
223
211
|
return color?.indexed === sentinel ? undefined : color;
|
|
224
212
|
}
|
|
225
|
-
const FILL_PATTERNS = [
|
|
226
|
-
undefined,
|
|
227
|
-
'solid',
|
|
228
|
-
'mediumGray',
|
|
229
|
-
'darkGray',
|
|
230
|
-
'lightGray',
|
|
231
|
-
'darkHorizontal',
|
|
232
|
-
'darkVertical',
|
|
233
|
-
'darkDown',
|
|
234
|
-
'darkUp',
|
|
235
|
-
'darkGrid',
|
|
236
|
-
'darkTrellis',
|
|
237
|
-
'lightHorizontal',
|
|
238
|
-
'lightVertical',
|
|
239
|
-
'lightDown',
|
|
240
|
-
'lightUp',
|
|
241
|
-
'lightGrid',
|
|
242
|
-
'lightTrellis',
|
|
243
|
-
'gray125',
|
|
244
|
-
'gray0625',
|
|
245
|
-
];
|
|
246
213
|
function readBorder(reader) {
|
|
247
214
|
const flags = reader.u8();
|
|
248
215
|
const top = readEdge(reader);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DateEpoch } from '../../core/date.ts';
|
|
1
2
|
import type { Worksheet } from '../../core/worksheet.ts';
|
|
2
3
|
import { type XfStyle } from '../style/xf-style.ts';
|
|
3
4
|
import { type FormulaScope } from './formula.ts';
|
|
@@ -6,4 +7,22 @@ import { type FormulaScope } from './formula.ts';
|
|
|
6
7
|
* non-empty cell with the style its index resolves to in `xfStyles` and, for a formula cell, the text
|
|
7
8
|
* its token stream decodes to through `scope`.
|
|
8
9
|
*/
|
|
9
|
-
export declare function parseWorksheet(part: Uint8Array,
|
|
10
|
+
export declare function parseWorksheet(part: Uint8Array, context: WorksheetReadContext): void;
|
|
11
|
+
/**
|
|
12
|
+
* What reading a worksheet part needs around it: the sheet being filled and the three tables its
|
|
13
|
+
* records resolve through, plus the date system a serial under a date format counts from.
|
|
14
|
+
*
|
|
15
|
+
* One value rather than five positional arguments, two of them arrays and two of those `readonly
|
|
16
|
+
* string[]`/`ReadonlyArray<XfStyle>` -- a run whose order the call site cannot be read against, on a
|
|
17
|
+
* reader that decides what every cell in the sheet says. It is also exactly the half of
|
|
18
|
+
* {@link CellRecordContext} that does not change between records, which is why that one extends it
|
|
19
|
+
* and the record loop spreads this one into it.
|
|
20
|
+
*/
|
|
21
|
+
export interface WorksheetReadContext {
|
|
22
|
+
readonly sheet: Worksheet;
|
|
23
|
+
readonly sharedStrings: readonly string[];
|
|
24
|
+
readonly xfStyles: ReadonlyArray<XfStyle>;
|
|
25
|
+
readonly scope: FormulaScope;
|
|
26
|
+
/** The workbook's date system, from `BrtWbProp`: what a serial under a date format counts from. */
|
|
27
|
+
readonly dateEpoch: DateEpoch;
|
|
28
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { encodeAddress,
|
|
2
|
-
import {
|
|
1
|
+
import { encodeAddress, encodeRect, MAX_COLUMN_INDEX, MAX_ROW_INDEX } from '../../core/address.js';
|
|
2
|
+
import { coerceDateSerial } from '../../core/date.js';
|
|
3
3
|
import { unmangleFunctions } from '../../core/formula.js';
|
|
4
4
|
import { assignStyleFacets } from '../../core/style.js';
|
|
5
5
|
import { applyXfToCell } from '../style/xf-style.js';
|
|
@@ -7,23 +7,31 @@ import { decodeFormula, formulaAnchor } from './formula.js';
|
|
|
7
7
|
import { errorCodeFor, RecordReader } from './primitives.js';
|
|
8
8
|
import { readRecords } from './record-stream.js';
|
|
9
9
|
import { BRT } from './record-types.js';
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
BRT.
|
|
17
|
-
BRT.
|
|
18
|
-
BRT.
|
|
10
|
+
const value = (read) => ({
|
|
11
|
+
kind: 'value',
|
|
12
|
+
read,
|
|
13
|
+
});
|
|
14
|
+
const formula = (read) => ({ kind: 'formula', read });
|
|
15
|
+
const CELL_RECORDS = new Map([
|
|
16
|
+
[BRT.CellBlank, value(() => null)],
|
|
17
|
+
[BRT.CellRk, value((r, c) => asNumberOrDate(r.rk(), c.numFmt, c.epoch))],
|
|
18
|
+
[BRT.CellReal, value((r, c) => asNumberOrDate(r.f64(), c.numFmt, c.epoch))],
|
|
19
|
+
[BRT.CellBool, value((r) => r.u8() !== 0)],
|
|
20
|
+
[BRT.CellError, value((r) => errorValueOrNull(r.u8()))],
|
|
21
|
+
[BRT.CellSt, value((r) => r.wideString())],
|
|
22
|
+
[BRT.CellRString, value((r) => r.richString())],
|
|
23
|
+
[BRT.CellIsst, value((r, c) => c.sharedStrings[r.u32()] ?? '')],
|
|
24
|
+
[BRT.FmlaNum, formula((r, c) => asNumberOrDate(r.f64(), c.numFmt, c.epoch))],
|
|
25
|
+
[BRT.FmlaBool, formula((r) => r.u8() !== 0)],
|
|
26
|
+
[BRT.FmlaError, formula((r) => errorValueOrNull(r.u8()) ?? undefined)],
|
|
27
|
+
[BRT.FmlaString, formula((r) => r.wideString())],
|
|
19
28
|
]);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
29
|
+
function errorValueOrNull(code) {
|
|
30
|
+
const error = errorCodeFor(code);
|
|
31
|
+
return error === undefined ? null : { error };
|
|
32
|
+
}
|
|
33
|
+
export function parseWorksheet(part, context) {
|
|
34
|
+
const { sheet, xfStyles, scope } = context;
|
|
27
35
|
let row = -1;
|
|
28
36
|
let rowStyle = -1;
|
|
29
37
|
const columnStyle = new Map();
|
|
@@ -31,6 +39,7 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
31
39
|
const groups = new Map();
|
|
32
40
|
const deferred = [];
|
|
33
41
|
for (const record of readRecords(part)) {
|
|
42
|
+
const cellRecord = CELL_RECORDS.get(record.type);
|
|
34
43
|
const reader = new RecordReader(record.data);
|
|
35
44
|
if (record.type === BRT.WsFmtInfo) {
|
|
36
45
|
reader.skip(6);
|
|
@@ -47,7 +56,12 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
47
56
|
else if (record.type === BRT.MergeCell) {
|
|
48
57
|
const { rowFirst, rowLast, colFirst, colLast } = reader.range();
|
|
49
58
|
if (inGrid(colFirst, rowFirst) && inGrid(colLast, rowLast)) {
|
|
50
|
-
sheet.mergeCells(
|
|
59
|
+
sheet.mergeCells(encodeRect({
|
|
60
|
+
top: rowFirst + 1,
|
|
61
|
+
left: colFirst + 1,
|
|
62
|
+
bottom: rowLast + 1,
|
|
63
|
+
right: colLast + 1,
|
|
64
|
+
}));
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
else if (record.type === BRT.ArrFmla) {
|
|
@@ -58,36 +72,10 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
58
72
|
rgcb: reader.bytes(reader.u32()),
|
|
59
73
|
});
|
|
60
74
|
}
|
|
61
|
-
else if (
|
|
62
|
-
const {
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
const resolved = styleIndex > 0 ? styleIndex : rowStyle >= 0 ? rowStyle : (columnStyle.get(column) ?? -1);
|
|
66
|
-
const style = resolved >= 0 ? xfStyles[resolved] : xfStyles[0];
|
|
67
|
-
const cell = sheet.getCell(encodeAddress(column + 1, row));
|
|
68
|
-
applyXfToCell(cell, style);
|
|
69
|
-
if (!FORMULA_RECORDS.has(record.type)) {
|
|
70
|
-
cell.value = decodeCell(record.type, reader, sharedStrings, style?.numFmt);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const result = cachedResult(record.type, reader, style?.numFmt);
|
|
74
|
-
reader.skip(2);
|
|
75
|
-
const rgce = reader.bytes(reader.u32());
|
|
76
|
-
const rgcb = reader.bytes(reader.u32());
|
|
77
|
-
const anchor = formulaAnchor(rgce, rgcb);
|
|
78
|
-
if (anchor === undefined) {
|
|
79
|
-
cell.value = formulaValue(decodeFormula(rgce, rgcb, scope), result);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
deferred.push({
|
|
83
|
-
cell,
|
|
84
|
-
row: row - 1,
|
|
85
|
-
column,
|
|
86
|
-
anchorRow: anchor.row,
|
|
87
|
-
anchorColumn: anchor.column,
|
|
88
|
-
result,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
75
|
+
else if (cellRecord !== undefined) {
|
|
76
|
+
const member = readCellRecord(cellRecord, reader, { ...context, row, rowStyle, columnStyle });
|
|
77
|
+
if (member !== undefined)
|
|
78
|
+
deferred.push(member);
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
81
|
for (const member of deferred) {
|
|
@@ -99,6 +87,40 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
99
87
|
: formulaValue(decodeFormula(group.rgce, group.rgcb, scope), member.result);
|
|
100
88
|
}
|
|
101
89
|
}
|
|
90
|
+
function readCellRecord(record, reader, context) {
|
|
91
|
+
const { sheet, sharedStrings, xfStyles, scope, dateEpoch, row, rowStyle, columnStyle } = context;
|
|
92
|
+
if (row <= 0)
|
|
93
|
+
return undefined;
|
|
94
|
+
const { column, styleIndex } = reader.cell();
|
|
95
|
+
if (!inGrid(column, row - 1))
|
|
96
|
+
return undefined;
|
|
97
|
+
const resolved = styleIndex > 0 ? styleIndex : rowStyle >= 0 ? rowStyle : (columnStyle.get(column) ?? -1);
|
|
98
|
+
const style = resolved >= 0 ? xfStyles[resolved] : xfStyles[0];
|
|
99
|
+
const cell = sheet.getCell(encodeAddress(column + 1, row));
|
|
100
|
+
applyXfToCell(cell, style);
|
|
101
|
+
const ctx = { sharedStrings, numFmt: style?.numFmt, epoch: dateEpoch };
|
|
102
|
+
if (record.kind === 'value') {
|
|
103
|
+
cell.value = record.read(reader, ctx);
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const result = record.read(reader, ctx);
|
|
107
|
+
reader.skip(2);
|
|
108
|
+
const rgce = reader.bytes(reader.u32());
|
|
109
|
+
const rgcb = reader.bytes(reader.u32());
|
|
110
|
+
const anchor = formulaAnchor(rgce, rgcb);
|
|
111
|
+
if (anchor === undefined) {
|
|
112
|
+
cell.value = formulaValue(decodeFormula(rgce, rgcb, scope), result);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
cell,
|
|
117
|
+
row: row - 1,
|
|
118
|
+
column,
|
|
119
|
+
anchorRow: anchor.row,
|
|
120
|
+
anchorColumn: anchor.column,
|
|
121
|
+
result,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
102
124
|
function groupKey(row, column) {
|
|
103
125
|
return `${row}:${column}`;
|
|
104
126
|
}
|
|
@@ -108,49 +130,11 @@ function formulaValue(formula, result) {
|
|
|
108
130
|
const stored = unmangleFunctions(formula);
|
|
109
131
|
return result === undefined ? { formula: stored } : { formula: stored, result };
|
|
110
132
|
}
|
|
111
|
-
function cachedResult(type, reader, numFmt) {
|
|
112
|
-
switch (type) {
|
|
113
|
-
case BRT.FmlaNum:
|
|
114
|
-
return asNumberOrDate(reader.f64(), numFmt);
|
|
115
|
-
case BRT.FmlaBool:
|
|
116
|
-
return reader.u8() !== 0;
|
|
117
|
-
case BRT.FmlaError: {
|
|
118
|
-
const error = errorCodeFor(reader.u8());
|
|
119
|
-
return error === undefined ? undefined : { error };
|
|
120
|
-
}
|
|
121
|
-
default:
|
|
122
|
-
return reader.wideString();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const MAX_ROW_INDEX = 1048575;
|
|
126
|
-
const MAX_COLUMN_INDEX = MAX_COLUMN - 1;
|
|
127
133
|
function inGrid(column, row) {
|
|
128
134
|
return column >= 0 && column <= MAX_COLUMN_INDEX && row >= 0 && row <= MAX_ROW_INDEX;
|
|
129
135
|
}
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
case BRT.CellRk:
|
|
133
|
-
return asNumberOrDate(reader.rk(), numFmt);
|
|
134
|
-
case BRT.CellReal:
|
|
135
|
-
return asNumberOrDate(reader.f64(), numFmt);
|
|
136
|
-
case BRT.CellBool:
|
|
137
|
-
return reader.u8() !== 0;
|
|
138
|
-
case BRT.CellError: {
|
|
139
|
-
const error = errorCodeFor(reader.u8());
|
|
140
|
-
return error === undefined ? null : { error };
|
|
141
|
-
}
|
|
142
|
-
case BRT.CellSt:
|
|
143
|
-
return reader.wideString();
|
|
144
|
-
case BRT.CellRString:
|
|
145
|
-
return reader.richString();
|
|
146
|
-
case BRT.CellIsst:
|
|
147
|
-
return sharedStrings[reader.u32()] ?? '';
|
|
148
|
-
default:
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function asNumberOrDate(value, numFmt) {
|
|
153
|
-
return numFmt !== undefined && isDateFormat(numFmt) ? serialToDate(value) : value;
|
|
136
|
+
function asNumberOrDate(value, numFmt, epoch) {
|
|
137
|
+
return coerceDateSerial(value, numFmt, epoch);
|
|
154
138
|
}
|
|
155
139
|
function applyRow(reader, sheet, defaultRowHeight) {
|
|
156
140
|
const index = reader.u32();
|
package/dist/io/xlsb/read.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Workbook } from '../../core/workbook.ts';
|
|
2
|
-
import {
|
|
2
|
+
import type { ReadPackageOptions } from '../opc/read-options.ts';
|
|
3
3
|
/** The office-document part every `.xlsb` package is entered through. */
|
|
4
4
|
export declare const XLSB_WORKBOOK_PART = "xl/workbook.bin";
|
|
5
5
|
/**
|
|
@@ -11,10 +11,13 @@ export declare const XLSB_WORKBOOK_PART = "xl/workbook.bin";
|
|
|
11
11
|
* @throws {PackageReadError} if the input is a ZIP that cannot be unpacked: a corrupt or
|
|
12
12
|
* truncated archive, or one exceeding the inflate bound (a probable zip bomb).
|
|
13
13
|
*/
|
|
14
|
-
export declare function readXlsb(data: Uint8Array, options?:
|
|
14
|
+
export declare function readXlsb(data: Uint8Array, options?: ReadPackageOptions): Workbook;
|
|
15
15
|
/**
|
|
16
16
|
* Build the model from an already-inflated `.xlsb` package. Separate from {@link readXlsb} so the
|
|
17
17
|
* `.xlsx` reader can hand over a package it has already inflated and classified, rather than
|
|
18
18
|
* inflating the same bytes twice.
|
|
19
|
+
*
|
|
20
|
+
* `documentPath` is where the package's own `_rels/.rels` says its workbook lives; it defaults to the
|
|
21
|
+
* conventional path for a caller holding nothing but the parts.
|
|
19
22
|
*/
|
|
20
|
-
export declare function readXlsbPackage(files: Record<string, Uint8Array
|
|
23
|
+
export declare function readXlsbPackage(files: Record<string, Uint8Array>, documentPath?: string): Workbook;
|
package/dist/io/xlsb/read.js
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
import { unmangleFunctions } from '../../core/formula.js';
|
|
2
2
|
import { INTERNAL } from '../../core/internal.js';
|
|
3
3
|
import { Workbook } from '../../core/workbook.js';
|
|
4
|
+
import { quoted } from '../../errors.js';
|
|
4
5
|
import { UnsupportedFormatError } from '../opc/errors.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { inflateSpreadsheetPackage } from '../opc/sniff-format.js';
|
|
6
|
+
import { openSpreadsheetPackage, packageAccessors, readPartRelationships } from '../opc/read-opc.js';
|
|
7
|
+
import { XlsbParseError } from './errors.js';
|
|
8
8
|
import { decodeFormula } from './formula.js';
|
|
9
9
|
import { RecordReader } from './primitives.js';
|
|
10
10
|
import { parseSharedStrings } from './read-shared-strings.js';
|
|
11
11
|
import { parseStyleTable } from './read-styles.js';
|
|
12
12
|
import { parseWorksheet } from './read-worksheet.js';
|
|
13
|
-
import { readRecords } from './record-stream.js';
|
|
13
|
+
import { blockTracker, readRecords } from './record-stream.js';
|
|
14
14
|
import { BRT } from './record-types.js';
|
|
15
15
|
export const XLSB_WORKBOOK_PART = 'xl/workbook.bin';
|
|
16
16
|
export function readXlsb(data, options = {}) {
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const { files, documentPath, workbookXml } = openSpreadsheetPackage(data, options.maxUncompressedBytes);
|
|
18
|
+
if (workbookXml !== undefined) {
|
|
19
|
+
throw new UnsupportedFormatError('unknown', `not a valid .xlsb package: its office document ${quoted(documentPath)} is XML, not the binary ${XLSB_WORKBOOK_PART} a .xlsb carries; read it with readXlsx`);
|
|
20
|
+
}
|
|
21
|
+
return readXlsbPackage(files, documentPath);
|
|
19
22
|
}
|
|
20
|
-
export function readXlsbPackage(files) {
|
|
23
|
+
export function readXlsbPackage(files, documentPath = XLSB_WORKBOOK_PART) {
|
|
21
24
|
const { partText, partBytes } = packageAccessors(files);
|
|
22
|
-
const workbookPart = partBytes(
|
|
25
|
+
const workbookPart = partBytes(documentPath);
|
|
23
26
|
if (workbookPart === undefined) {
|
|
24
|
-
throw new UnsupportedFormatError('unknown', `not a valid .xlsb package: ${
|
|
27
|
+
throw new UnsupportedFormatError('unknown', `not a valid .xlsb package: ${quoted(documentPath)} is missing`);
|
|
25
28
|
}
|
|
26
|
-
const rels =
|
|
27
|
-
const sharedStrings = parseSharedStrings(partBytes('xl/sharedStrings.bin'));
|
|
28
|
-
const { cellXfs, namedStyles, defaultFont } = parseStyleTable(partBytes('xl/styles.bin'));
|
|
29
|
+
const rels = readPartRelationships(documentPath, partText, partBytes);
|
|
30
|
+
const sharedStrings = parseSharedStrings(rels.relatedBytes('sharedStrings') ?? partBytes('xl/sharedStrings.bin'));
|
|
31
|
+
const { cellXfs, namedStyles, defaultFont } = parseStyleTable(rels.relatedBytes('styles') ?? partBytes('xl/styles.bin'));
|
|
29
32
|
const workbook = new Workbook();
|
|
30
33
|
if (namedStyles.length > 1)
|
|
31
34
|
workbook[INTERNAL].restoreNamedStyles(namedStyles);
|
|
32
35
|
workbook[INTERNAL].restoreDefaultFont(defaultFont);
|
|
33
36
|
const declaration = readWorkbookPart(workbookPart);
|
|
37
|
+
workbook.dateEpoch = declaration.dateEpoch;
|
|
34
38
|
const scope = {
|
|
35
39
|
sheetNames: declaration.sheets.map((sheet) => sheet.name),
|
|
36
40
|
externSheets: declaration.externSheets,
|
|
@@ -39,39 +43,45 @@ export function readXlsbPackage(files) {
|
|
|
39
43
|
};
|
|
40
44
|
for (const declared of declaration.sheets) {
|
|
41
45
|
const sheet = workbook.addWorksheet(declared.name, { state: declared.state });
|
|
42
|
-
const target = declared.relId === undefined ? undefined : rels.
|
|
43
|
-
const part = target === undefined ? undefined : partBytes(
|
|
46
|
+
const target = declared.relId === undefined ? undefined : rels.byId(declared.relId)?.target;
|
|
47
|
+
const part = target === undefined ? undefined : partBytes(rels.pathOf(target));
|
|
44
48
|
if (part !== undefined)
|
|
45
|
-
parseWorksheet(part,
|
|
49
|
+
parseWorksheet(part, {
|
|
50
|
+
sheet,
|
|
51
|
+
sharedStrings,
|
|
52
|
+
xfStyles: cellXfs,
|
|
53
|
+
scope,
|
|
54
|
+
dateEpoch: declaration.dateEpoch,
|
|
55
|
+
});
|
|
46
56
|
}
|
|
47
57
|
for (const defined of definedNames(declaration, scope))
|
|
48
58
|
workbook.defineName(defined);
|
|
49
59
|
return workbook;
|
|
50
60
|
}
|
|
61
|
+
const WORKBOOK_BLOCKS = [
|
|
62
|
+
[BRT.BeginBundleShs, BRT.EndBundleShs, 'bundle'],
|
|
63
|
+
[BRT.BeginExternals, BRT.EndExternals, 'externals'],
|
|
64
|
+
];
|
|
51
65
|
function readWorkbookPart(part) {
|
|
52
66
|
const sheets = [];
|
|
53
67
|
const names = [];
|
|
54
68
|
let externSheets = [];
|
|
55
|
-
|
|
56
|
-
let inExternals = false;
|
|
69
|
+
const blocks = blockTracker(WORKBOOK_BLOCKS);
|
|
57
70
|
let supportingBooks = 0;
|
|
58
71
|
let selfSupBook;
|
|
72
|
+
let dateEpoch = 1900;
|
|
59
73
|
for (const record of readRecords(part)) {
|
|
60
|
-
if (record.type
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else if (record.type === BRT.BundleSh &&
|
|
74
|
+
if (blocks.boundary(record.type))
|
|
75
|
+
continue;
|
|
76
|
+
if (record.type === BRT.WbProp)
|
|
77
|
+
dateEpoch = readDateEpoch(record.data);
|
|
78
|
+
else if (record.type === BRT.BundleSh && blocks.isOpen('bundle'))
|
|
65
79
|
sheets.push(readSheet(record.data));
|
|
66
|
-
else if (record.type === BRT.BeginExternals)
|
|
67
|
-
inExternals = true;
|
|
68
|
-
else if (record.type === BRT.EndExternals)
|
|
69
|
-
inExternals = false;
|
|
70
80
|
else if (record.type === BRT.ExternSheet)
|
|
71
81
|
externSheets = readExternSheets(record.data);
|
|
72
82
|
else if (record.type === BRT.SupSelf)
|
|
73
83
|
selfSupBook = supportingBooks++;
|
|
74
|
-
else if (
|
|
84
|
+
else if (blocks.isOpen('externals'))
|
|
75
85
|
supportingBooks++;
|
|
76
86
|
else if (record.type === BRT.Name)
|
|
77
87
|
names.push(readName(record.data));
|
|
@@ -81,8 +91,14 @@ function readWorkbookPart(part) {
|
|
|
81
91
|
names,
|
|
82
92
|
externSheets,
|
|
83
93
|
selfSupBook: supportingBooks === 1 ? selfSupBook : undefined,
|
|
94
|
+
dateEpoch,
|
|
84
95
|
};
|
|
85
96
|
}
|
|
97
|
+
function readDateEpoch(data) {
|
|
98
|
+
if (data.length < 4)
|
|
99
|
+
return 1900;
|
|
100
|
+
return (new RecordReader(data).u32() & 1) === 0 ? 1900 : 1904;
|
|
101
|
+
}
|
|
86
102
|
function readSheet(data) {
|
|
87
103
|
const reader = new RecordReader(data);
|
|
88
104
|
const state = SHEET_STATES[reader.u32()] ?? 'visible';
|
|
@@ -93,9 +109,11 @@ function readSheet(data) {
|
|
|
93
109
|
function readExternSheets(data) {
|
|
94
110
|
const reader = new RecordReader(data);
|
|
95
111
|
const count = reader.u32();
|
|
112
|
+
if (count * XTI_BYTES > reader.remaining) {
|
|
113
|
+
throw new XlsbParseError(`BIFF12 externSheet table declares ${count} entries needing ${count * XTI_BYTES} bytes but ` +
|
|
114
|
+
`only ${reader.remaining} remain in the record`);
|
|
115
|
+
}
|
|
96
116
|
const entries = [];
|
|
97
|
-
if (count * XTI_BYTES > reader.remaining)
|
|
98
|
-
return entries;
|
|
99
117
|
for (let index = 0; index < count; index++) {
|
|
100
118
|
entries.push({ supBook: reader.u32(), firstSheet: reader.i32(), lastSheet: reader.i32() });
|
|
101
119
|
}
|
|
@@ -12,3 +12,33 @@ export interface BiffRecord {
|
|
|
12
12
|
* end of the part.
|
|
13
13
|
*/
|
|
14
14
|
export declare function readRecords(part: Uint8Array): Generator<BiffRecord>;
|
|
15
|
+
/**
|
|
16
|
+
* Track which `Begin…`/`End…` blocks a BIFF12 record stream is currently inside.
|
|
17
|
+
*
|
|
18
|
+
* BIFF12 is flat: a collection is a `BeginFonts` record, its members, then `EndFonts`, and a reader
|
|
19
|
+
* has to carry that state itself. Two readers here did, and only one did it legibly. The style reader
|
|
20
|
+
* kept an explicit collection with start and end tables; the workbook reader re-solved it with loose
|
|
21
|
+
* booleans in an if-else chain whose *ordering was load-bearing and unstated*: move the `EndExternals`
|
|
22
|
+
* arm below the catch-all that counts records inside the externals block and the count silently goes
|
|
23
|
+
* wrong. Answering "is this record a block boundary" separately from "what does this record mean"
|
|
24
|
+
* removes that constraint rather than documenting it.
|
|
25
|
+
*
|
|
26
|
+
* Each end names the block it closes, so an `EndFonts` cannot close `<fills>` the way a bare set of
|
|
27
|
+
* end markers allowed. Blocks are tracked independently, so two that a damaged file interleaves stay
|
|
28
|
+
* separate rather than one clearing the other.
|
|
29
|
+
*
|
|
30
|
+
* A block is declared as one `[start, end, block]` triple rather than as an entry in a start table
|
|
31
|
+
* and a matching entry in an end table. The two-table form let a caller pair `BeginFills` with
|
|
32
|
+
* `EndFonts` in a way that compiles and reads plausibly, which is the very mistake the paragraph
|
|
33
|
+
* above says this exists to prevent.
|
|
34
|
+
*/
|
|
35
|
+
export declare function blockTracker<T>(blocks: readonly (readonly [number, number, T])[]): BlockTracker<T>;
|
|
36
|
+
export interface BlockTracker<T> {
|
|
37
|
+
/** Whether the stream is currently inside `block`. */
|
|
38
|
+
isOpen(block: T): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Take a record's type as a possible block boundary, returning whether it was one. A boundary
|
|
41
|
+
* carries no content of its own, so a caller that gets `true` skips the record entirely.
|
|
42
|
+
*/
|
|
43
|
+
boundary(type: number): boolean;
|
|
44
|
+
}
|
|
@@ -26,3 +26,23 @@ function byteAt(part, index) {
|
|
|
26
26
|
throw new XlsbParseError('truncated BIFF12 record header');
|
|
27
27
|
return value;
|
|
28
28
|
}
|
|
29
|
+
export function blockTracker(blocks) {
|
|
30
|
+
const starts = new Map(blocks.map(([start, , block]) => [start, block]));
|
|
31
|
+
const ends = new Map(blocks.map(([, end, block]) => [end, block]));
|
|
32
|
+
const open = new Set();
|
|
33
|
+
return {
|
|
34
|
+
isOpen: (block) => open.has(block),
|
|
35
|
+
boundary(type) {
|
|
36
|
+
const ended = ends.get(type);
|
|
37
|
+
if (ended !== undefined) {
|
|
38
|
+
open.delete(ended);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const started = starts.get(type);
|
|
42
|
+
if (started === undefined)
|
|
43
|
+
return false;
|
|
44
|
+
open.add(started);
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|