xls-codec 0.0.0 → 1.0.1
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 +21 -0
- package/README.md +128 -0
- package/dist/biff/cursor.cjs +73 -0
- package/dist/biff/cursor.d.cts +28 -0
- package/dist/biff/cursor.d.ts +28 -0
- package/dist/biff/cursor.js +72 -0
- package/dist/biff/errors.cjs +18 -0
- package/dist/biff/errors.d.cts +5 -0
- package/dist/biff/errors.d.ts +5 -0
- package/dist/biff/errors.js +17 -0
- package/dist/biff/record-types.cjs +108 -0
- package/dist/biff/record-types.d.cts +73 -0
- package/dist/biff/record-types.d.ts +73 -0
- package/dist/biff/record-types.js +73 -0
- package/dist/biff/records.cjs +40 -0
- package/dist/biff/records.d.cts +2 -0
- package/dist/biff/records.d.ts +2 -0
- package/dist/biff/records.js +38 -0
- package/dist/biff/rk.cjs +29 -0
- package/dist/biff/rk.d.cts +5 -0
- package/dist/biff/rk.d.ts +5 -0
- package/dist/biff/rk.js +28 -0
- package/dist/biff/strings.cjs +66 -0
- package/dist/biff/strings.d.cts +14 -0
- package/dist/biff/strings.d.ts +14 -0
- package/dist/biff/strings.js +63 -0
- package/dist/biff/substreams.cjs +71 -0
- package/dist/biff/substreams.d.cts +21 -0
- package/dist/biff/substreams.d.ts +21 -0
- package/dist/biff/substreams.js +69 -0
- package/dist/container.cjs +46 -0
- package/dist/container.d.cts +15 -0
- package/dist/container.d.ts +15 -0
- package/dist/container.js +44 -0
- package/dist/content.cjs +230 -0
- package/dist/content.d.cts +20 -0
- package/dist/content.d.ts +20 -0
- package/dist/content.js +228 -0
- package/dist/index.cjs +74 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/number-format.cjs +298 -0
- package/dist/number-format.d.cts +32 -0
- package/dist/number-format.d.ts +32 -0
- package/dist/number-format.js +296 -0
- package/dist/records-DVIqXFKk.d.cts +20 -0
- package/dist/records-DVIqXFKk.d.ts +20 -0
- package/dist/serial.cjs +62 -0
- package/dist/serial.d.cts +7 -0
- package/dist/serial.d.ts +7 -0
- package/dist/serial.js +59 -0
- package/dist/units.cjs +30 -0
- package/dist/units.d.cts +11 -0
- package/dist/units.d.ts +11 -0
- package/dist/units.js +28 -0
- package/dist/workbook/globals.cjs +106 -0
- package/dist/workbook/globals.d.cts +45 -0
- package/dist/workbook/globals.d.ts +45 -0
- package/dist/workbook/globals.js +104 -0
- package/dist/workbook/sheet.cjs +372 -0
- package/dist/workbook/sheet.d.cts +58 -0
- package/dist/workbook/sheet.d.ts +58 -0
- package/dist/workbook/sheet.js +371 -0
- package/package.json +85 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_biff_record_types = require("../biff/record-types.cjs");
|
|
3
|
+
const require_biff_records = require("../biff/records.cjs");
|
|
4
|
+
const require_number_format = require("../number-format.cjs");
|
|
5
|
+
const require_biff_cursor = require("../biff/cursor.cjs");
|
|
6
|
+
const require_biff_strings = require("../biff/strings.cjs");
|
|
7
|
+
//#region src/workbook/globals.ts
|
|
8
|
+
/** [MS-XLS] 2.4.28's own hsState values; 0x01 is Hidden and 0x02 Very Hidden. */
|
|
9
|
+
const HIDDEN_STATE_MASK = 3;
|
|
10
|
+
const HIDDEN_STATE_VISIBLE = 0;
|
|
11
|
+
/** A cell format's fStyle bit within the XF record's third 16-bit field. */
|
|
12
|
+
const XF_FLAG_STYLE = 4;
|
|
13
|
+
/** An SST declaring more unique strings than the record could possibly carry is malformed; a string is at minimum three bytes (cch, flags) so this bounds the loop against the actual data. */
|
|
14
|
+
const MIN_SST_ENTRY_BYTES = 3;
|
|
15
|
+
/** Reads the globals substream's records into the tables the sheet reader needs. */
|
|
16
|
+
function readWorkbookGlobals(records) {
|
|
17
|
+
const sheets = [];
|
|
18
|
+
const cellFormats = [];
|
|
19
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
20
|
+
let sharedStrings = [];
|
|
21
|
+
let date1904 = false;
|
|
22
|
+
for (const record of records) switch (record.type) {
|
|
23
|
+
case 133:
|
|
24
|
+
sheets.push(readBoundSheet(record));
|
|
25
|
+
break;
|
|
26
|
+
case 252:
|
|
27
|
+
sharedStrings = readSharedStrings(record);
|
|
28
|
+
break;
|
|
29
|
+
case require_biff_record_types.RECORD_FORMAT: {
|
|
30
|
+
const format = readFormat(record);
|
|
31
|
+
customFormats.set(format.id, format.code);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case 224:
|
|
35
|
+
cellFormats.push(readCellFormat(record));
|
|
36
|
+
break;
|
|
37
|
+
case 34: date1904 = readDate1904(record);
|
|
38
|
+
}
|
|
39
|
+
const numberFormats = new Map(require_number_format.BUILTIN_NUMBER_FORMATS);
|
|
40
|
+
for (const [id, code] of customFormats) numberFormats.set(id, code);
|
|
41
|
+
return {
|
|
42
|
+
sheets,
|
|
43
|
+
sharedStrings,
|
|
44
|
+
cellFormats,
|
|
45
|
+
numberFormats,
|
|
46
|
+
date1904
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** BoundSheet8 ([MS-XLS] 2.4.28): a four-byte stream position, a byte of hidden state, a byte of sheet type, then the name as a ShortXLUnicodeString. */
|
|
50
|
+
function readBoundSheet(record) {
|
|
51
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
52
|
+
const bofPosition = cursor.u32();
|
|
53
|
+
const state = cursor.u8();
|
|
54
|
+
const sheetType = cursor.u8();
|
|
55
|
+
return {
|
|
56
|
+
name: require_biff_strings.readShortXLUnicodeString(cursor),
|
|
57
|
+
hidden: (state & HIDDEN_STATE_MASK) !== HIDDEN_STATE_VISIBLE,
|
|
58
|
+
sheetType,
|
|
59
|
+
bofPosition
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** SST ([MS-XLS] 2.4.265): a total reference count, a unique-string count, then that many XLUnicodeRichExtendedStrings -- packed, with no offsets, so a single mis-sized string desynchronises every string after it. This is why the string reader consumes each one's trailing formatting-run and phonetic payloads exactly rather than ignoring them. */
|
|
63
|
+
function readSharedStrings(record) {
|
|
64
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
65
|
+
cursor.i32();
|
|
66
|
+
const uniqueCount = cursor.i32();
|
|
67
|
+
if (uniqueCount < 0) throw new require_biff_records.BiffFormatError(`SST declares a negative unique-string count (${uniqueCount})`);
|
|
68
|
+
const totalBytes = record.blocks.reduce((sum, block) => sum + block.length, 0);
|
|
69
|
+
if (uniqueCount * MIN_SST_ENTRY_BYTES > totalBytes) throw new require_biff_records.BiffFormatError(`SST declares ${uniqueCount} unique strings, more than its ${totalBytes} bytes could carry`);
|
|
70
|
+
const strings = [];
|
|
71
|
+
for (let index = 0; index < uniqueCount; index += 1) strings.push(require_biff_strings.readRichExtendedString(cursor));
|
|
72
|
+
return strings;
|
|
73
|
+
}
|
|
74
|
+
/** Format ([MS-XLS] 2.4.126): a two-byte identifier then the format string as an XLUnicodeString. */
|
|
75
|
+
function readFormat(record) {
|
|
76
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
77
|
+
return {
|
|
78
|
+
id: cursor.u16(),
|
|
79
|
+
code: require_biff_strings.readXLUnicodeString(cursor)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** XF ([MS-XLS] 2.4.353): a font index, a number-format identifier, then a flags field whose fStyle bit says whether the trailing payload is a CellXF or a StyleXF. */
|
|
83
|
+
function readCellFormat(record) {
|
|
84
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
85
|
+
return {
|
|
86
|
+
fontIndex: cursor.u16(),
|
|
87
|
+
formatId: cursor.u16(),
|
|
88
|
+
isStyle: (cursor.u16() & XF_FLAG_STYLE) !== 0
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Date1904 ([MS-XLS] 2.4.77): a two-byte boolean. */
|
|
92
|
+
function readDate1904(record) {
|
|
93
|
+
return new require_biff_cursor.BlockCursor(record.blocks).u16() !== 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The number-format code a cell's own XF index resolves to, or undefined when it resolves to none.
|
|
97
|
+
*
|
|
98
|
+
* Undefined is a real answer rather than a failure: [MS-XLS] permits an XF to name a reserved identifier that no built-in code covers, and ContentSheetCell's own numberFormatCode is documented as absent for a cell with no producer-declared format -- never a fabricated empty string or a silently substituted General.
|
|
99
|
+
*/
|
|
100
|
+
function formatCodeOf(globals, xfIndex) {
|
|
101
|
+
const format = globals.cellFormats[xfIndex];
|
|
102
|
+
return format === void 0 ? void 0 : globals.numberFormats.get(format.formatId);
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
exports.formatCodeOf = formatCodeOf;
|
|
106
|
+
exports.readWorkbookGlobals = readWorkbookGlobals;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RecordGroup } from "../biff/substreams.cjs";
|
|
2
|
+
//#region src/workbook/globals.d.ts
|
|
3
|
+
/** One sheet, as its BoundSheet8 record describes it ([MS-XLS] 2.4.28). */
|
|
4
|
+
interface SheetEntry {
|
|
5
|
+
/** The sheet's name, from stName. */
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** True for both the Hidden and Very Hidden states -- the schema's own ContentSheet has no place for either, so the distinction is not carried further. */
|
|
8
|
+
readonly hidden: boolean;
|
|
9
|
+
/** The dt field: 0x00 worksheet or dialog sheet, 0x01 macro sheet, 0x02 chart sheet, 0x06 VBA module. */
|
|
10
|
+
readonly sheetType: number;
|
|
11
|
+
/** lbPlyPos: the byte offset, within the workbook stream, of this sheet's own BOF record. */
|
|
12
|
+
readonly bofPosition: number;
|
|
13
|
+
}
|
|
14
|
+
/** One XF record's fixed prefix ([MS-XLS] 2.4.353). Only the fields the value path needs are carried: the trailing CellXF/StyleXF payload holds alignment, fill, and border properties whose colours are palette indices needing the Palette record to resolve, which this package does not yet read. */
|
|
15
|
+
interface CellFormat {
|
|
16
|
+
/** ifnt: the index of the Font record this format uses. */
|
|
17
|
+
readonly fontIndex: number;
|
|
18
|
+
/** ifmt: the number-format identifier, resolved against the Format records and the built-in table. */
|
|
19
|
+
readonly formatId: number;
|
|
20
|
+
/** fStyle: true when this record describes a cell STYLE rather than a cell format. */
|
|
21
|
+
readonly isStyle: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Everything the globals substream contributes to reading the sheets that follow it. */
|
|
24
|
+
interface WorkbookGlobals {
|
|
25
|
+
/** The sheets, in BoundSheet8 order -- which is the workbook's own tab order, and not necessarily the order the substreams appear in the stream. */
|
|
26
|
+
readonly sheets: readonly SheetEntry[];
|
|
27
|
+
/** The shared string table, indexed by a LabelSst record's isst. */
|
|
28
|
+
readonly sharedStrings: readonly string[];
|
|
29
|
+
/** The XF table, indexed by a cell's own ixfe. */
|
|
30
|
+
readonly cellFormats: readonly CellFormat[];
|
|
31
|
+
/** Number-format codes by identifier: the file's own Format records laid over the built-in table. */
|
|
32
|
+
readonly numberFormats: ReadonlyMap<number, string>;
|
|
33
|
+
/** Whether serials count from the 1904 epoch rather than the 1900 one ([MS-XLS] 2.4.77). */
|
|
34
|
+
readonly date1904: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Reads the globals substream's records into the tables the sheet reader needs. */
|
|
37
|
+
declare function readWorkbookGlobals(records: readonly RecordGroup[]): WorkbookGlobals;
|
|
38
|
+
/**
|
|
39
|
+
* The number-format code a cell's own XF index resolves to, or undefined when it resolves to none.
|
|
40
|
+
*
|
|
41
|
+
* Undefined is a real answer rather than a failure: [MS-XLS] permits an XF to name a reserved identifier that no built-in code covers, and ContentSheetCell's own numberFormatCode is documented as absent for a cell with no producer-declared format -- never a fabricated empty string or a silently substituted General.
|
|
42
|
+
*/
|
|
43
|
+
declare function formatCodeOf(globals: WorkbookGlobals, xfIndex: number): string | undefined;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { CellFormat, SheetEntry, WorkbookGlobals, formatCodeOf, readWorkbookGlobals };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RecordGroup } from "../biff/substreams.js";
|
|
2
|
+
//#region src/workbook/globals.d.ts
|
|
3
|
+
/** One sheet, as its BoundSheet8 record describes it ([MS-XLS] 2.4.28). */
|
|
4
|
+
interface SheetEntry {
|
|
5
|
+
/** The sheet's name, from stName. */
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** True for both the Hidden and Very Hidden states -- the schema's own ContentSheet has no place for either, so the distinction is not carried further. */
|
|
8
|
+
readonly hidden: boolean;
|
|
9
|
+
/** The dt field: 0x00 worksheet or dialog sheet, 0x01 macro sheet, 0x02 chart sheet, 0x06 VBA module. */
|
|
10
|
+
readonly sheetType: number;
|
|
11
|
+
/** lbPlyPos: the byte offset, within the workbook stream, of this sheet's own BOF record. */
|
|
12
|
+
readonly bofPosition: number;
|
|
13
|
+
}
|
|
14
|
+
/** One XF record's fixed prefix ([MS-XLS] 2.4.353). Only the fields the value path needs are carried: the trailing CellXF/StyleXF payload holds alignment, fill, and border properties whose colours are palette indices needing the Palette record to resolve, which this package does not yet read. */
|
|
15
|
+
interface CellFormat {
|
|
16
|
+
/** ifnt: the index of the Font record this format uses. */
|
|
17
|
+
readonly fontIndex: number;
|
|
18
|
+
/** ifmt: the number-format identifier, resolved against the Format records and the built-in table. */
|
|
19
|
+
readonly formatId: number;
|
|
20
|
+
/** fStyle: true when this record describes a cell STYLE rather than a cell format. */
|
|
21
|
+
readonly isStyle: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Everything the globals substream contributes to reading the sheets that follow it. */
|
|
24
|
+
interface WorkbookGlobals {
|
|
25
|
+
/** The sheets, in BoundSheet8 order -- which is the workbook's own tab order, and not necessarily the order the substreams appear in the stream. */
|
|
26
|
+
readonly sheets: readonly SheetEntry[];
|
|
27
|
+
/** The shared string table, indexed by a LabelSst record's isst. */
|
|
28
|
+
readonly sharedStrings: readonly string[];
|
|
29
|
+
/** The XF table, indexed by a cell's own ixfe. */
|
|
30
|
+
readonly cellFormats: readonly CellFormat[];
|
|
31
|
+
/** Number-format codes by identifier: the file's own Format records laid over the built-in table. */
|
|
32
|
+
readonly numberFormats: ReadonlyMap<number, string>;
|
|
33
|
+
/** Whether serials count from the 1904 epoch rather than the 1900 one ([MS-XLS] 2.4.77). */
|
|
34
|
+
readonly date1904: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Reads the globals substream's records into the tables the sheet reader needs. */
|
|
37
|
+
declare function readWorkbookGlobals(records: readonly RecordGroup[]): WorkbookGlobals;
|
|
38
|
+
/**
|
|
39
|
+
* The number-format code a cell's own XF index resolves to, or undefined when it resolves to none.
|
|
40
|
+
*
|
|
41
|
+
* Undefined is a real answer rather than a failure: [MS-XLS] permits an XF to name a reserved identifier that no built-in code covers, and ContentSheetCell's own numberFormatCode is documented as absent for a cell with no producer-declared format -- never a fabricated empty string or a silently substituted General.
|
|
42
|
+
*/
|
|
43
|
+
declare function formatCodeOf(globals: WorkbookGlobals, xfIndex: number): string | undefined;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { CellFormat, SheetEntry, WorkbookGlobals, formatCodeOf, readWorkbookGlobals };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { RECORD_FORMAT } from "../biff/record-types.js";
|
|
2
|
+
import { BiffFormatError } from "../biff/records.js";
|
|
3
|
+
import { BUILTIN_NUMBER_FORMATS } from "../number-format.js";
|
|
4
|
+
import { BlockCursor } from "../biff/cursor.js";
|
|
5
|
+
import { readRichExtendedString, readShortXLUnicodeString, readXLUnicodeString } from "../biff/strings.js";
|
|
6
|
+
//#region src/workbook/globals.ts
|
|
7
|
+
/** [MS-XLS] 2.4.28's own hsState values; 0x01 is Hidden and 0x02 Very Hidden. */
|
|
8
|
+
const HIDDEN_STATE_MASK = 3;
|
|
9
|
+
const HIDDEN_STATE_VISIBLE = 0;
|
|
10
|
+
/** A cell format's fStyle bit within the XF record's third 16-bit field. */
|
|
11
|
+
const XF_FLAG_STYLE = 4;
|
|
12
|
+
/** An SST declaring more unique strings than the record could possibly carry is malformed; a string is at minimum three bytes (cch, flags) so this bounds the loop against the actual data. */
|
|
13
|
+
const MIN_SST_ENTRY_BYTES = 3;
|
|
14
|
+
/** Reads the globals substream's records into the tables the sheet reader needs. */
|
|
15
|
+
function readWorkbookGlobals(records) {
|
|
16
|
+
const sheets = [];
|
|
17
|
+
const cellFormats = [];
|
|
18
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
19
|
+
let sharedStrings = [];
|
|
20
|
+
let date1904 = false;
|
|
21
|
+
for (const record of records) switch (record.type) {
|
|
22
|
+
case 133:
|
|
23
|
+
sheets.push(readBoundSheet(record));
|
|
24
|
+
break;
|
|
25
|
+
case 252:
|
|
26
|
+
sharedStrings = readSharedStrings(record);
|
|
27
|
+
break;
|
|
28
|
+
case RECORD_FORMAT: {
|
|
29
|
+
const format = readFormat(record);
|
|
30
|
+
customFormats.set(format.id, format.code);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 224:
|
|
34
|
+
cellFormats.push(readCellFormat(record));
|
|
35
|
+
break;
|
|
36
|
+
case 34: date1904 = readDate1904(record);
|
|
37
|
+
}
|
|
38
|
+
const numberFormats = new Map(BUILTIN_NUMBER_FORMATS);
|
|
39
|
+
for (const [id, code] of customFormats) numberFormats.set(id, code);
|
|
40
|
+
return {
|
|
41
|
+
sheets,
|
|
42
|
+
sharedStrings,
|
|
43
|
+
cellFormats,
|
|
44
|
+
numberFormats,
|
|
45
|
+
date1904
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** BoundSheet8 ([MS-XLS] 2.4.28): a four-byte stream position, a byte of hidden state, a byte of sheet type, then the name as a ShortXLUnicodeString. */
|
|
49
|
+
function readBoundSheet(record) {
|
|
50
|
+
const cursor = new BlockCursor(record.blocks);
|
|
51
|
+
const bofPosition = cursor.u32();
|
|
52
|
+
const state = cursor.u8();
|
|
53
|
+
const sheetType = cursor.u8();
|
|
54
|
+
return {
|
|
55
|
+
name: readShortXLUnicodeString(cursor),
|
|
56
|
+
hidden: (state & HIDDEN_STATE_MASK) !== HIDDEN_STATE_VISIBLE,
|
|
57
|
+
sheetType,
|
|
58
|
+
bofPosition
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** SST ([MS-XLS] 2.4.265): a total reference count, a unique-string count, then that many XLUnicodeRichExtendedStrings -- packed, with no offsets, so a single mis-sized string desynchronises every string after it. This is why the string reader consumes each one's trailing formatting-run and phonetic payloads exactly rather than ignoring them. */
|
|
62
|
+
function readSharedStrings(record) {
|
|
63
|
+
const cursor = new BlockCursor(record.blocks);
|
|
64
|
+
cursor.i32();
|
|
65
|
+
const uniqueCount = cursor.i32();
|
|
66
|
+
if (uniqueCount < 0) throw new BiffFormatError(`SST declares a negative unique-string count (${uniqueCount})`);
|
|
67
|
+
const totalBytes = record.blocks.reduce((sum, block) => sum + block.length, 0);
|
|
68
|
+
if (uniqueCount * MIN_SST_ENTRY_BYTES > totalBytes) throw new BiffFormatError(`SST declares ${uniqueCount} unique strings, more than its ${totalBytes} bytes could carry`);
|
|
69
|
+
const strings = [];
|
|
70
|
+
for (let index = 0; index < uniqueCount; index += 1) strings.push(readRichExtendedString(cursor));
|
|
71
|
+
return strings;
|
|
72
|
+
}
|
|
73
|
+
/** Format ([MS-XLS] 2.4.126): a two-byte identifier then the format string as an XLUnicodeString. */
|
|
74
|
+
function readFormat(record) {
|
|
75
|
+
const cursor = new BlockCursor(record.blocks);
|
|
76
|
+
return {
|
|
77
|
+
id: cursor.u16(),
|
|
78
|
+
code: readXLUnicodeString(cursor)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** XF ([MS-XLS] 2.4.353): a font index, a number-format identifier, then a flags field whose fStyle bit says whether the trailing payload is a CellXF or a StyleXF. */
|
|
82
|
+
function readCellFormat(record) {
|
|
83
|
+
const cursor = new BlockCursor(record.blocks);
|
|
84
|
+
return {
|
|
85
|
+
fontIndex: cursor.u16(),
|
|
86
|
+
formatId: cursor.u16(),
|
|
87
|
+
isStyle: (cursor.u16() & XF_FLAG_STYLE) !== 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Date1904 ([MS-XLS] 2.4.77): a two-byte boolean. */
|
|
91
|
+
function readDate1904(record) {
|
|
92
|
+
return new BlockCursor(record.blocks).u16() !== 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The number-format code a cell's own XF index resolves to, or undefined when it resolves to none.
|
|
96
|
+
*
|
|
97
|
+
* Undefined is a real answer rather than a failure: [MS-XLS] permits an XF to name a reserved identifier that no built-in code covers, and ContentSheetCell's own numberFormatCode is documented as absent for a cell with no producer-declared format -- never a fabricated empty string or a silently substituted General.
|
|
98
|
+
*/
|
|
99
|
+
function formatCodeOf(globals, xfIndex) {
|
|
100
|
+
const format = globals.cellFormats[xfIndex];
|
|
101
|
+
return format === void 0 ? void 0 : globals.numberFormats.get(format.formatId);
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { formatCodeOf, readWorkbookGlobals };
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../biff/record-types.cjs");
|
|
3
|
+
const require_biff_records = require("../biff/records.cjs");
|
|
4
|
+
const require_biff_cursor = require("../biff/cursor.cjs");
|
|
5
|
+
const require_biff_strings = require("../biff/strings.cjs");
|
|
6
|
+
const require_biff_errors = require("../biff/errors.cjs");
|
|
7
|
+
const require_biff_rk = require("../biff/rk.cjs");
|
|
8
|
+
const require_units = require("../units.cjs");
|
|
9
|
+
//#region src/workbook/sheet.ts
|
|
10
|
+
/** Row record flag bits, in the 32-bit field following unused1 ([MS-XLS] 2.4.221). */
|
|
11
|
+
const ROW_FLAG_HIDDEN = 32;
|
|
12
|
+
/** fUnsynced: the row height was set manually. When clear the height is the sheet default rather than a per-row declaration. */
|
|
13
|
+
const ROW_FLAG_UNSYNCED = 64;
|
|
14
|
+
/** ColInfo flag bits ([MS-XLS] 2.4.53). */
|
|
15
|
+
const COLINFO_FLAG_HIDDEN = 1;
|
|
16
|
+
/** A FormulaValue whose fExprO field is this is not an Xnum but a tagged non-numeric value ([MS-XLS] 2.5.133). */
|
|
17
|
+
const FORMULA_VALUE_TAGGED = 65535;
|
|
18
|
+
/** The tag byte's own vocabulary in that case. */
|
|
19
|
+
const FORMULA_VALUE_STRING = 0;
|
|
20
|
+
const FORMULA_VALUE_BOOLEAN = 1;
|
|
21
|
+
const FORMULA_VALUE_ERROR = 2;
|
|
22
|
+
const FORMULA_VALUE_BLANK = 3;
|
|
23
|
+
/** MulRk carries rw, colFirst, N six-byte RkRecs, then colLast; MulBlank the same with two-byte ixfe entries. */
|
|
24
|
+
const MUL_FIXED_BYTES = 6;
|
|
25
|
+
const MULRK_ENTRY_BYTES = 6;
|
|
26
|
+
const MULBLANK_ENTRY_BYTES = 2;
|
|
27
|
+
/** Reads one worksheet substream's records. */
|
|
28
|
+
function readSheetRecords(records, sharedStrings) {
|
|
29
|
+
const cells = [];
|
|
30
|
+
const rows = [];
|
|
31
|
+
const columns = [];
|
|
32
|
+
const merges = [];
|
|
33
|
+
let usedRange;
|
|
34
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
35
|
+
const record = records[index];
|
|
36
|
+
if (record === void 0) continue;
|
|
37
|
+
switch (record.type) {
|
|
38
|
+
case 512:
|
|
39
|
+
usedRange = readDimensions(record);
|
|
40
|
+
break;
|
|
41
|
+
case 520:
|
|
42
|
+
rows.push(readRow(record));
|
|
43
|
+
break;
|
|
44
|
+
case 125:
|
|
45
|
+
columns.push(...readColInfo(record));
|
|
46
|
+
break;
|
|
47
|
+
case 229:
|
|
48
|
+
merges.push(...readMergeCells(record));
|
|
49
|
+
break;
|
|
50
|
+
case 513:
|
|
51
|
+
cells.push(readBlank(record));
|
|
52
|
+
break;
|
|
53
|
+
case 190:
|
|
54
|
+
cells.push(...readMulBlank(record));
|
|
55
|
+
break;
|
|
56
|
+
case 638:
|
|
57
|
+
cells.push(readRk(record));
|
|
58
|
+
break;
|
|
59
|
+
case 189:
|
|
60
|
+
cells.push(...readMulRk(record));
|
|
61
|
+
break;
|
|
62
|
+
case 515:
|
|
63
|
+
cells.push(readNumber(record));
|
|
64
|
+
break;
|
|
65
|
+
case 517: {
|
|
66
|
+
const cell = readBoolErr(record);
|
|
67
|
+
if (cell !== void 0) cells.push(cell);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 253:
|
|
71
|
+
cells.push(readLabelSst(record, sharedStrings));
|
|
72
|
+
break;
|
|
73
|
+
case 516:
|
|
74
|
+
cells.push(readLabel(record));
|
|
75
|
+
break;
|
|
76
|
+
case 6: cells.push(readFormula(record, stringResultAfter(records, index)));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return usedRange === void 0 ? {
|
|
80
|
+
cells,
|
|
81
|
+
rows,
|
|
82
|
+
columns,
|
|
83
|
+
merges
|
|
84
|
+
} : {
|
|
85
|
+
cells,
|
|
86
|
+
rows,
|
|
87
|
+
columns,
|
|
88
|
+
merges,
|
|
89
|
+
usedRange
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Finds the String record carrying a Formula's cached string result, if it has one.
|
|
94
|
+
*
|
|
95
|
+
* [MS-XLS] 2.5.133 says the String "immediately follows" the Formula, but the FORMULA production of [MS-XLS] 2.1.7.20.6 is wider than that -- `[Uncalced] Formula [Array / Table / ShrFmla / SUB] [String *Continue]` -- so an array formula, a data table, or a member of a shared-formula run puts one record in between. Skipping exactly those three finds the String in both shapes; anything else ends the search, so a Formula with no string result never reaches past its own cell into the next one's records.
|
|
96
|
+
*/
|
|
97
|
+
function stringResultAfter(records, formulaIndex) {
|
|
98
|
+
for (let index = formulaIndex + 1; index < records.length; index += 1) {
|
|
99
|
+
const candidate = records[index];
|
|
100
|
+
if (candidate === void 0) return;
|
|
101
|
+
if (candidate.type === 519) return candidate;
|
|
102
|
+
if (candidate.type !== 545 && candidate.type !== 566 && candidate.type !== 1212) return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Dimensions ([MS-XLS] 2.4.90): a four-byte first row, a four-byte past-the-end row, a two-byte first column, and a two-byte past-the-end column. */
|
|
106
|
+
function readDimensions(record) {
|
|
107
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
108
|
+
const rwMic = cursor.u32();
|
|
109
|
+
const rwMac = cursor.u32();
|
|
110
|
+
const colMic = cursor.u16();
|
|
111
|
+
const colMac = cursor.u16();
|
|
112
|
+
if (rwMac === 0 || colMac === 0) return;
|
|
113
|
+
return {
|
|
114
|
+
startRow: rwMic,
|
|
115
|
+
startColumn: colMic,
|
|
116
|
+
endRow: rwMac - 1,
|
|
117
|
+
endColumn: colMac - 1
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Row ([MS-XLS] 2.4.221): sixteen bytes, of which this reader uses the index, the height, and the hidden and manually-set flags. */
|
|
121
|
+
function readRow(record) {
|
|
122
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
123
|
+
const index = cursor.u16();
|
|
124
|
+
cursor.skip(4);
|
|
125
|
+
const heightTwips = cursor.u16();
|
|
126
|
+
cursor.skip(4);
|
|
127
|
+
const flags = cursor.u8();
|
|
128
|
+
const hidden = (flags & ROW_FLAG_HIDDEN) !== 0;
|
|
129
|
+
const declared = (flags & ROW_FLAG_UNSYNCED) !== 0;
|
|
130
|
+
const heightPt = require_units.twipsToPoints(heightTwips);
|
|
131
|
+
return declared && heightPt > 0 ? {
|
|
132
|
+
index,
|
|
133
|
+
heightPt,
|
|
134
|
+
hidden
|
|
135
|
+
} : {
|
|
136
|
+
index,
|
|
137
|
+
hidden
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** ColInfo ([MS-XLS] 2.4.53): one record covering an inclusive RANGE of columns, so it expands to one entry per column it names. */
|
|
141
|
+
function readColInfo(record) {
|
|
142
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
143
|
+
const first = cursor.u16();
|
|
144
|
+
const last = cursor.u16();
|
|
145
|
+
const coldx = cursor.u16();
|
|
146
|
+
cursor.skip(2);
|
|
147
|
+
const hidden = (cursor.u16() & COLINFO_FLAG_HIDDEN) !== 0;
|
|
148
|
+
const widthPt = require_units.columnWidthToPoints(coldx);
|
|
149
|
+
const columns = [];
|
|
150
|
+
const end = Math.min(last, 255);
|
|
151
|
+
for (let index = first; index <= end; index += 1) columns.push(widthPt > 0 ? {
|
|
152
|
+
index,
|
|
153
|
+
widthPt,
|
|
154
|
+
hidden
|
|
155
|
+
} : {
|
|
156
|
+
index,
|
|
157
|
+
hidden
|
|
158
|
+
});
|
|
159
|
+
return columns;
|
|
160
|
+
}
|
|
161
|
+
/** MergeCells ([MS-XLS] 2.4.168): a count then that many Ref8 structures ([MS-XLS] 2.5.208), each an inclusive row and column range. */
|
|
162
|
+
function readMergeCells(record) {
|
|
163
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
164
|
+
const count = cursor.u16();
|
|
165
|
+
const ranges = [];
|
|
166
|
+
for (let index = 0; index < count; index += 1) {
|
|
167
|
+
const startRow = cursor.u16();
|
|
168
|
+
const endRow = cursor.u16();
|
|
169
|
+
const startColumn = cursor.u16();
|
|
170
|
+
const endColumn = cursor.u16();
|
|
171
|
+
ranges.push({
|
|
172
|
+
startRow,
|
|
173
|
+
startColumn,
|
|
174
|
+
endRow,
|
|
175
|
+
endColumn
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return ranges;
|
|
179
|
+
}
|
|
180
|
+
/** The Cell structure ([MS-XLS] 2.5.19) every single-cell record opens with. */
|
|
181
|
+
function readCellHeader(cursor) {
|
|
182
|
+
return {
|
|
183
|
+
row: cursor.u16(),
|
|
184
|
+
column: cursor.u16(),
|
|
185
|
+
xfIndex: cursor.u16()
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function readBlank(record) {
|
|
189
|
+
return {
|
|
190
|
+
...readCellHeader(new require_biff_cursor.BlockCursor(record.blocks)),
|
|
191
|
+
value: { kind: "blank" },
|
|
192
|
+
fromFormula: false
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** MulBlank ([MS-XLS] 2.4.174): a run of blank cells in one row, each with its own format index. The entry count is derived from the record's own length rather than from colLast, which sits AFTER the variable-length array. */
|
|
196
|
+
function readMulBlank(record) {
|
|
197
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
198
|
+
const row = cursor.u16();
|
|
199
|
+
const first = cursor.u16();
|
|
200
|
+
const count = mulEntryCount(record, MULBLANK_ENTRY_BYTES);
|
|
201
|
+
const cells = [];
|
|
202
|
+
for (let offset = 0; offset < count; offset += 1) cells.push({
|
|
203
|
+
row,
|
|
204
|
+
column: first + offset,
|
|
205
|
+
xfIndex: cursor.u16(),
|
|
206
|
+
value: { kind: "blank" },
|
|
207
|
+
fromFormula: false
|
|
208
|
+
});
|
|
209
|
+
return cells;
|
|
210
|
+
}
|
|
211
|
+
function readRk(record) {
|
|
212
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
213
|
+
return {
|
|
214
|
+
row: cursor.u16(),
|
|
215
|
+
column: cursor.u16(),
|
|
216
|
+
xfIndex: cursor.u16(),
|
|
217
|
+
value: {
|
|
218
|
+
kind: "number",
|
|
219
|
+
value: require_biff_rk.decodeRkNumber(cursor.u32())
|
|
220
|
+
},
|
|
221
|
+
fromFormula: false
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** MulRk ([MS-XLS] 2.4.175): a run of RK-encoded numeric cells in one row, each an RkRec ([MS-XLS] 2.5.218) of a format index and an RK word. */
|
|
225
|
+
function readMulRk(record) {
|
|
226
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
227
|
+
const row = cursor.u16();
|
|
228
|
+
const first = cursor.u16();
|
|
229
|
+
const count = mulEntryCount(record, MULRK_ENTRY_BYTES);
|
|
230
|
+
const cells = [];
|
|
231
|
+
for (let offset = 0; offset < count; offset += 1) {
|
|
232
|
+
const xfIndex = cursor.u16();
|
|
233
|
+
cells.push({
|
|
234
|
+
row,
|
|
235
|
+
column: first + offset,
|
|
236
|
+
xfIndex,
|
|
237
|
+
value: {
|
|
238
|
+
kind: "number",
|
|
239
|
+
value: require_biff_rk.decodeRkNumber(cursor.u32())
|
|
240
|
+
},
|
|
241
|
+
fromFormula: false
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return cells;
|
|
245
|
+
}
|
|
246
|
+
/** Both Mul records put colLast after their variable-length array, so the entry count follows from the record's own length: total = rw + colFirst + N*entry + colLast. */
|
|
247
|
+
function mulEntryCount(record, entryBytes) {
|
|
248
|
+
const total = record.blocks.reduce((sum, block) => sum + block.length, 0);
|
|
249
|
+
const payload = total - MUL_FIXED_BYTES;
|
|
250
|
+
if (payload < 0 || payload % entryBytes !== 0) throw new require_biff_records.BiffFormatError(`multiple-cell record of ${total} bytes does not hold a whole number of ${entryBytes}-byte entries`);
|
|
251
|
+
return payload / entryBytes;
|
|
252
|
+
}
|
|
253
|
+
function readNumber(record) {
|
|
254
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
255
|
+
return {
|
|
256
|
+
...readCellHeader(cursor),
|
|
257
|
+
value: {
|
|
258
|
+
kind: "number",
|
|
259
|
+
value: cursor.f64()
|
|
260
|
+
},
|
|
261
|
+
fromFormula: false
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/** BoolErr ([MS-XLS] 2.4.24): a Cell then a Bes ([MS-XLS] 2.5.10), whose second byte says whether the first is a boolean or an error code. An error code the specification does not define yields no cell at all rather than a fabricated spelling. */
|
|
265
|
+
function readBoolErr(record) {
|
|
266
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
267
|
+
const header = readCellHeader(cursor);
|
|
268
|
+
const value = cursor.u8();
|
|
269
|
+
if (!(cursor.u8() !== 0)) return {
|
|
270
|
+
...header,
|
|
271
|
+
value: {
|
|
272
|
+
kind: "boolean",
|
|
273
|
+
value: value !== 0
|
|
274
|
+
},
|
|
275
|
+
fromFormula: false
|
|
276
|
+
};
|
|
277
|
+
const text = require_biff_errors.errorTextOf(value);
|
|
278
|
+
return text === void 0 ? void 0 : {
|
|
279
|
+
...header,
|
|
280
|
+
value: {
|
|
281
|
+
kind: "error",
|
|
282
|
+
value: text
|
|
283
|
+
},
|
|
284
|
+
fromFormula: false
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/** LabelSst ([MS-XLS] 2.4.149): a Cell then a four-byte index into the shared string table. An index the table does not hold yields an empty string rather than throwing, since one dangling index should not fail a whole workbook. */
|
|
288
|
+
function readLabelSst(record, sharedStrings) {
|
|
289
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
290
|
+
const header = readCellHeader(cursor);
|
|
291
|
+
const index = cursor.u32();
|
|
292
|
+
return {
|
|
293
|
+
...header,
|
|
294
|
+
value: {
|
|
295
|
+
kind: "string",
|
|
296
|
+
value: sharedStrings[index] ?? ""
|
|
297
|
+
},
|
|
298
|
+
fromFormula: false
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Label ([MS-XLS] 2.4.148): a Cell then an inline XLUnicodeString.
|
|
303
|
+
*
|
|
304
|
+
* In BIFF8 proper a string cell is a LabelSst indexing the shared string table, and [MS-XLS] documents this record in its charting sense. It is read as a string cell anyway because the layout is identical either way and third-party producers -- and files converted up from BIFF5, where Label WAS the string cell record -- still emit it in a worksheet's cell table, where reading it costs nothing and skipping it would silently drop real text.
|
|
305
|
+
*/
|
|
306
|
+
function readLabel(record) {
|
|
307
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
308
|
+
return {
|
|
309
|
+
...readCellHeader(cursor),
|
|
310
|
+
value: {
|
|
311
|
+
kind: "string",
|
|
312
|
+
value: require_biff_strings.readXLUnicodeString(cursor)
|
|
313
|
+
},
|
|
314
|
+
fromFormula: false
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Formula ([MS-XLS] 2.4.127): a Cell, an eight-byte FormulaValue, flags, a calculation cache, then the compiled expression.
|
|
319
|
+
*
|
|
320
|
+
* Only the cached VALUE is read. The expression is a CellParsedFormula -- a compiled Ptg token stream rather than text -- and turning one back into a formula string means implementing the whole Ptg vocabulary plus shared-formula and external-reference resolution, which is its own substantial piece of work. So the cell's displayed value is correct and its `formula` field stays absent, rather than a plausible-looking expression being invented for it.
|
|
321
|
+
*/
|
|
322
|
+
function readFormula(record, next) {
|
|
323
|
+
const cursor = new require_biff_cursor.BlockCursor(record.blocks);
|
|
324
|
+
const header = readCellHeader(cursor);
|
|
325
|
+
const bytes = cursor.take(8);
|
|
326
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
327
|
+
if (!(view.getUint16(6, true) === FORMULA_VALUE_TAGGED)) return {
|
|
328
|
+
...header,
|
|
329
|
+
value: {
|
|
330
|
+
kind: "number",
|
|
331
|
+
value: view.getFloat64(0, true)
|
|
332
|
+
},
|
|
333
|
+
fromFormula: true
|
|
334
|
+
};
|
|
335
|
+
return {
|
|
336
|
+
...header,
|
|
337
|
+
value: taggedFormulaValue(view, next),
|
|
338
|
+
fromFormula: true
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/** The non-numeric readings of a FormulaValue ([MS-XLS] 2.5.133), selected by its first byte. */
|
|
342
|
+
function taggedFormulaValue(view, next) {
|
|
343
|
+
switch (view.getUint8(0)) {
|
|
344
|
+
case FORMULA_VALUE_STRING:
|
|
345
|
+
if (next?.type !== 519) return {
|
|
346
|
+
kind: "string",
|
|
347
|
+
value: ""
|
|
348
|
+
};
|
|
349
|
+
return {
|
|
350
|
+
kind: "string",
|
|
351
|
+
value: require_biff_strings.readXLUnicodeString(new require_biff_cursor.BlockCursor(next.blocks))
|
|
352
|
+
};
|
|
353
|
+
case FORMULA_VALUE_BOOLEAN: return {
|
|
354
|
+
kind: "boolean",
|
|
355
|
+
value: view.getUint8(2) !== 0
|
|
356
|
+
};
|
|
357
|
+
case FORMULA_VALUE_ERROR: {
|
|
358
|
+
const text = require_biff_errors.errorTextOf(view.getUint8(2));
|
|
359
|
+
return text === void 0 ? { kind: "blank" } : {
|
|
360
|
+
kind: "error",
|
|
361
|
+
value: text
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
case FORMULA_VALUE_BLANK: return {
|
|
365
|
+
kind: "string",
|
|
366
|
+
value: ""
|
|
367
|
+
};
|
|
368
|
+
default: return { kind: "blank" };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
exports.readSheetRecords = readSheetRecords;
|