xls-codec 1.0.2 → 2.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/README.md +128 -23
- package/dist/biff/bof-writer.cjs +18 -0
- package/dist/biff/bof-writer.d.cts +5 -0
- package/dist/biff/bof-writer.d.ts +5 -0
- package/dist/biff/bof-writer.js +17 -0
- package/dist/biff/builder.cjs +48 -0
- package/dist/biff/builder.d.cts +17 -0
- package/dist/biff/builder.d.ts +17 -0
- package/dist/biff/builder.js +47 -0
- package/dist/biff/cursor.cjs +4 -0
- package/dist/biff/cursor.d.cts +1 -27
- package/dist/biff/cursor.d.ts +1 -27
- package/dist/biff/cursor.js +4 -0
- package/dist/biff/errors.cjs +7 -0
- package/dist/biff/errors.d.cts +3 -1
- package/dist/biff/errors.d.ts +3 -1
- package/dist/biff/errors.js +7 -1
- package/dist/biff/print-setup.cjs +108 -0
- package/dist/biff/print-setup.d.cts +2 -0
- package/dist/biff/print-setup.d.ts +2 -0
- package/dist/biff/print-setup.js +103 -0
- package/dist/biff/ptg-functions.cjs +383 -0
- package/dist/biff/ptg-functions.d.cts +7 -0
- package/dist/biff/ptg-functions.d.ts +7 -0
- package/dist/biff/ptg-functions.js +381 -0
- package/dist/biff/ptg.cjs +319 -0
- package/dist/biff/ptg.d.cts +2 -0
- package/dist/biff/ptg.d.ts +2 -0
- package/dist/biff/ptg.js +318 -0
- package/dist/biff/record-types.cjs +60 -0
- package/dist/biff/record-types.d.cts +41 -1
- package/dist/biff/record-types.d.ts +41 -1
- package/dist/biff/record-types.js +41 -1
- package/dist/biff/record-writer.cjs +29 -0
- package/dist/biff/record-writer.d.cts +7 -0
- package/dist/biff/record-writer.d.ts +7 -0
- package/dist/biff/record-writer.js +27 -0
- package/dist/biff/string-writer.cjs +58 -0
- package/dist/biff/string-writer.d.cts +13 -0
- package/dist/biff/string-writer.d.ts +13 -0
- package/dist/biff/string-writer.js +55 -0
- package/dist/biff/strings.d.cts +1 -1
- package/dist/biff/strings.d.ts +1 -1
- package/dist/biff/substreams.d.cts +1 -20
- package/dist/biff/substreams.d.ts +1 -20
- package/dist/biff/write-errors.cjs +10 -0
- package/dist/biff/write-errors.d.cts +6 -0
- package/dist/biff/write-errors.d.ts +6 -0
- package/dist/biff/write-errors.js +9 -0
- package/dist/biff/xf-colors.cjs +374 -0
- package/dist/biff/xf-colors.d.cts +2 -0
- package/dist/biff/xf-colors.d.ts +2 -0
- package/dist/biff/xf-colors.js +339 -0
- package/dist/biff/xf-writer.cjs +96 -0
- package/dist/biff/xf-writer.d.cts +29 -0
- package/dist/biff/xf-writer.d.ts +29 -0
- package/dist/biff/xf-writer.js +90 -0
- package/dist/container.cjs +13 -4
- package/dist/container.d.cts +10 -3
- package/dist/container.d.ts +10 -3
- package/dist/container.js +12 -4
- package/dist/content.cjs +123 -18
- package/dist/content.js +123 -18
- package/dist/cursor-VMtw9uVP.d.cts +30 -0
- package/dist/cursor-VMtw9uVP.d.ts +30 -0
- package/dist/index.cjs +71 -5
- package/dist/index.d.cts +20 -10
- package/dist/index.d.ts +20 -10
- package/dist/index.js +17 -7
- package/dist/metadata.cjs +14 -0
- package/dist/metadata.d.cts +6 -0
- package/dist/metadata.d.ts +6 -0
- package/dist/metadata.js +13 -0
- package/dist/print-names-D-njuzVw.d.cts +41 -0
- package/dist/print-names-DUlpVE00.d.ts +41 -0
- package/dist/print-setup-B_ihDvm5.d.cts +57 -0
- package/dist/print-setup-B_ihDvm5.d.ts +57 -0
- package/dist/ptg-B2K8t3js.d.cts +21 -0
- package/dist/ptg-B2K8t3js.d.ts +21 -0
- package/dist/serial.cjs +41 -0
- package/dist/serial.d.cts +7 -1
- package/dist/serial.d.ts +7 -1
- package/dist/serial.js +39 -1
- package/dist/substreams-D7dQiJbp.d.ts +21 -0
- package/dist/substreams-Ddtvn_Vr.d.cts +21 -0
- package/dist/units.cjs +37 -0
- package/dist/units.d.cts +15 -1
- package/dist/units.d.ts +15 -1
- package/dist/units.js +33 -1
- package/dist/workbook/globals-writer.cjs +122 -0
- package/dist/workbook/globals-writer.d.cts +39 -0
- package/dist/workbook/globals-writer.d.ts +39 -0
- package/dist/workbook/globals-writer.js +120 -0
- package/dist/workbook/globals.cjs +72 -8
- package/dist/workbook/globals.d.cts +26 -2
- package/dist/workbook/globals.d.ts +26 -2
- package/dist/workbook/globals.js +71 -7
- package/dist/workbook/print-names.cjs +241 -0
- package/dist/workbook/print-names.d.cts +2 -0
- package/dist/workbook/print-names.d.ts +2 -0
- package/dist/workbook/print-names.js +238 -0
- package/dist/workbook/sheet-writer.cjs +277 -0
- package/dist/workbook/sheet-writer.d.cts +12 -0
- package/dist/workbook/sheet-writer.d.ts +12 -0
- package/dist/workbook/sheet-writer.js +276 -0
- package/dist/workbook/sheet.cjs +125 -17
- package/dist/workbook/sheet.d.cts +38 -4
- package/dist/workbook/sheet.d.ts +38 -4
- package/dist/workbook/sheet.js +125 -17
- package/dist/write.cjs +311 -0
- package/dist/write.d.cts +13 -0
- package/dist/write.d.ts +13 -0
- package/dist/write.js +309 -0
- package/dist/written-cells.cjs +26 -0
- package/dist/written-cells.d.cts +16 -0
- package/dist/written-cells.d.ts +16 -0
- package/dist/written-cells.js +24 -0
- package/dist/xf-colors-CehHZtBy.d.cts +91 -0
- package/dist/xf-colors-CpykR3B9.d.ts +91 -0
- package/package.json +4 -3
- package/dist/number-format.cjs +0 -298
- package/dist/number-format.d.cts +0 -32
- package/dist/number-format.d.ts +0 -32
- package/dist/number-format.js +0 -296
package/dist/container.d.cts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
//#region src/container.d.ts
|
|
2
|
+
/** The [MS-OLEPS] Property Set Stream a .xls's title/author/dates live in when present ([MS-OSHARED] 2.3.3.2.2) -- a genuinely optional stream, unlike Workbook, since a valid BIFF8 workbook need not carry document properties at all. */
|
|
3
|
+
declare const SUMMARY_INFORMATION_STREAM = "SummaryInformation";
|
|
4
|
+
interface WorkbookStreams {
|
|
5
|
+
readonly workbook: Uint8Array<ArrayBuffer>;
|
|
6
|
+
/** The raw "\x05SummaryInformation" stream bytes, or undefined when the container carries none. */
|
|
7
|
+
readonly metadata: Uint8Array<ArrayBuffer> | undefined;
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
|
-
* Extracts the BIFF8 record stream from a .xls file's compound-file container.
|
|
10
|
+
* Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
|
|
4
11
|
*
|
|
5
12
|
* Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
|
|
6
13
|
*/
|
|
7
|
-
declare function
|
|
14
|
+
declare function readWorkbookStreams(bytes: Uint8Array<ArrayBuffer>): WorkbookStreams;
|
|
8
15
|
/**
|
|
9
16
|
* Whether these bytes are a workbook this package can read.
|
|
10
17
|
*
|
|
@@ -12,4 +19,4 @@ declare function readWorkbookStream(bytes: Uint8Array<ArrayBuffer>): Uint8Array<
|
|
|
12
19
|
*/
|
|
13
20
|
declare function isXlsFile(bytes: Uint8Array<ArrayBuffer>): boolean;
|
|
14
21
|
//#endregion
|
|
15
|
-
export { isXlsFile,
|
|
22
|
+
export { SUMMARY_INFORMATION_STREAM, WorkbookStreams, isXlsFile, readWorkbookStreams };
|
package/dist/container.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
//#region src/container.d.ts
|
|
2
|
+
/** The [MS-OLEPS] Property Set Stream a .xls's title/author/dates live in when present ([MS-OSHARED] 2.3.3.2.2) -- a genuinely optional stream, unlike Workbook, since a valid BIFF8 workbook need not carry document properties at all. */
|
|
3
|
+
declare const SUMMARY_INFORMATION_STREAM = "SummaryInformation";
|
|
4
|
+
interface WorkbookStreams {
|
|
5
|
+
readonly workbook: Uint8Array<ArrayBuffer>;
|
|
6
|
+
/** The raw "\x05SummaryInformation" stream bytes, or undefined when the container carries none. */
|
|
7
|
+
readonly metadata: Uint8Array<ArrayBuffer> | undefined;
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
|
-
* Extracts the BIFF8 record stream from a .xls file's compound-file container.
|
|
10
|
+
* Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
|
|
4
11
|
*
|
|
5
12
|
* Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
|
|
6
13
|
*/
|
|
7
|
-
declare function
|
|
14
|
+
declare function readWorkbookStreams(bytes: Uint8Array<ArrayBuffer>): WorkbookStreams;
|
|
8
15
|
/**
|
|
9
16
|
* Whether these bytes are a workbook this package can read.
|
|
10
17
|
*
|
|
@@ -12,4 +19,4 @@ declare function readWorkbookStream(bytes: Uint8Array<ArrayBuffer>): Uint8Array<
|
|
|
12
19
|
*/
|
|
13
20
|
declare function isXlsFile(bytes: Uint8Array<ArrayBuffer>): boolean;
|
|
14
21
|
//#endregion
|
|
15
|
-
export { isXlsFile,
|
|
22
|
+
export { SUMMARY_INFORMATION_STREAM, WorkbookStreams, isXlsFile, readWorkbookStreams };
|
package/dist/container.js
CHANGED
|
@@ -5,16 +5,24 @@ import { CompoundFileFormatError, isCompoundFile, readCompoundFile } from "archi
|
|
|
5
5
|
const WORKBOOK_STREAM = "Workbook";
|
|
6
6
|
/** BIFF5 and BIFF7 workbooks name their stream "Book" instead. Recognised only to say so in an error, never read: those are different formats record-for-record, not older spellings of this one. */
|
|
7
7
|
const LEGACY_WORKBOOK_STREAM = "Book";
|
|
8
|
+
/** The [MS-OLEPS] Property Set Stream a .xls's title/author/dates live in when present ([MS-OSHARED] 2.3.3.2.2) -- a genuinely optional stream, unlike Workbook, since a valid BIFF8 workbook need not carry document properties at all. */
|
|
9
|
+
const SUMMARY_INFORMATION_STREAM = "SummaryInformation";
|
|
8
10
|
/**
|
|
9
|
-
* Extracts the BIFF8 record stream from a .xls file's compound-file container.
|
|
11
|
+
* Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
|
|
10
12
|
*
|
|
11
13
|
* Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
|
|
12
14
|
*/
|
|
13
|
-
function
|
|
15
|
+
function readWorkbookStreams(bytes) {
|
|
14
16
|
if (!isCompoundFile(bytes)) throw new BiffFormatError("not a compound file: a .xls workbook is a [MS-CFB] container holding a 'Workbook' stream");
|
|
15
17
|
const streams = readWorkbookContainer(bytes);
|
|
16
18
|
const workbook = streams.find((stream) => stream.path === WORKBOOK_STREAM);
|
|
17
|
-
if (workbook !== void 0)
|
|
19
|
+
if (workbook !== void 0) {
|
|
20
|
+
const metadata = streams.find((stream) => stream.path === SUMMARY_INFORMATION_STREAM);
|
|
21
|
+
return {
|
|
22
|
+
workbook: workbook.bytes,
|
|
23
|
+
metadata: metadata?.bytes
|
|
24
|
+
};
|
|
25
|
+
}
|
|
18
26
|
if (streams.some((stream) => stream.path === LEGACY_WORKBOOK_STREAM)) throw new BiffFormatError("compound file holds a 'Book' stream rather than a 'Workbook' stream, so it is a BIFF5/BIFF7 workbook; this reader implements BIFF8 only");
|
|
19
27
|
throw new BiffFormatError("compound file holds no 'Workbook' stream, so it is not a .xls workbook");
|
|
20
28
|
}
|
|
@@ -41,4 +49,4 @@ function isXlsFile(bytes) {
|
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
//#endregion
|
|
44
|
-
export { isXlsFile,
|
|
52
|
+
export { SUMMARY_INFORMATION_STREAM, isXlsFile, readWorkbookStreams };
|
package/dist/content.cjs
CHANGED
|
@@ -2,48 +2,99 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
require("./biff/record-types.cjs");
|
|
3
3
|
const require_biff_records = require("./biff/records.cjs");
|
|
4
4
|
const require_container = require("./container.cjs");
|
|
5
|
+
const require_units = require("./units.cjs");
|
|
6
|
+
const require_biff_print_setup = require("./biff/print-setup.cjs");
|
|
5
7
|
const require_biff_substreams = require("./biff/substreams.cjs");
|
|
6
|
-
const
|
|
8
|
+
const require_biff_xf_colors = require("./biff/xf-colors.cjs");
|
|
7
9
|
const require_serial = require("./serial.cjs");
|
|
8
10
|
const require_workbook_globals = require("./workbook/globals.cjs");
|
|
9
11
|
const require_workbook_sheet = require("./workbook/sheet.cjs");
|
|
12
|
+
let archive_codec = require("archive-codec");
|
|
10
13
|
let document_schema_js = require("document-schema.js");
|
|
14
|
+
let excel_number_format = require("excel-number-format");
|
|
11
15
|
//#region src/content.ts
|
|
12
16
|
/** Which BoundSheet8 dt values name a sheet this reader maps. 0x00 is a worksheet or dialog sheet; macro sheets, chart sheets, and VBA modules carry no cell table for ContentSheet to hold. */
|
|
13
17
|
const SHEET_TYPE_WORKSHEET = 0;
|
|
14
18
|
/**
|
|
15
|
-
*
|
|
19
|
+
* Excel's own "Normal" page-setup preset, the per-field fallback for a print setting the file states nothing about.
|
|
16
20
|
*
|
|
17
|
-
* ContentSheetPrintSettings makes pageSize, margins, gridlines, headers, and pageOrder REQUIRED,
|
|
21
|
+
* ContentSheetPrintSettings makes pageSize, margins, gridlines, headers, and pageOrder REQUIRED, while [MS-XLS] 2.1.7.20.6's own PAGESETUP production makes every record behind them optional -- so a sheet whose page setup was never touched genuinely carries no Setup and no margin records, and something has to stand in. These are the values Excel itself calls Normal (top/bottom 0.75in, left/right 0.7in, on Letter paper, gridlines and row/column headers not printed, pages down-then-over), and the identical constants ooxml.js falls back to for an xlsx carrying no pageMargins element -- so the same untouched sheet reads the same either way.
|
|
22
|
+
*
|
|
23
|
+
* Each field falls back independently: a sheet that declares a left margin and nothing else keeps its real left margin and takes the preset for the other three, rather than the whole preset displacing the one value the file actually stated.
|
|
18
24
|
*/
|
|
19
|
-
const POINTS_PER_INCH = 72;
|
|
20
25
|
const DEFAULT_PRINT_SETTINGS = {
|
|
21
26
|
pageSize: document_schema_js.PAGE_SIZE_LETTER,
|
|
22
27
|
margins: {
|
|
23
|
-
topPt: .75
|
|
24
|
-
rightPt: .7
|
|
25
|
-
bottomPt: .75
|
|
26
|
-
leftPt: .7
|
|
28
|
+
topPt: require_units.inchesToPoints(.75),
|
|
29
|
+
rightPt: require_units.inchesToPoints(.7),
|
|
30
|
+
bottomPt: require_units.inchesToPoints(.75),
|
|
31
|
+
leftPt: require_units.inchesToPoints(.7)
|
|
27
32
|
},
|
|
28
33
|
gridlines: false,
|
|
29
34
|
headers: false,
|
|
30
35
|
pageOrder: "downThenOver"
|
|
31
36
|
};
|
|
32
37
|
/**
|
|
38
|
+
* The iScale value that says "print at actual size", which is what ContentSheetPrintSettings already means by carrying no scalePercent at all.
|
|
39
|
+
*
|
|
40
|
+
* Setup's own iScale is a mandatory field of a mandatory record, with no spelling for "this sheet declares no scale" -- so an untouched sheet still states 100. Reporting that as an explicit scalePercent would put a field on every sheet of every workbook read, carrying nothing a consumer could act on that its absence does not already say, and would mean a document written with no scale came back with one. The two spellings render identically, so collapsing them onto the absent one is lossless in both directions; a scale that is genuinely anything else is reported exactly as the file states it.
|
|
41
|
+
*/
|
|
42
|
+
const ACTUAL_SIZE_SCALE_PERCENT = 100;
|
|
43
|
+
/**
|
|
44
|
+
* The print settings a sheet's own records and its built-in print names state, with the Normal preset filling in what they do not.
|
|
45
|
+
*
|
|
46
|
+
* Two of BIFF8's own conditional rules are honoured rather than flattened. A Setup record whose fNoPls bit is set declares its own paper size and scale undefined ([MS-XLS] 2.4.257: "whether the iPaperSize, iScale, iRes, iVRes, iCopies, fNoOrient, and fPortrait data are undefined and ignored"), so neither is read from it -- the page size falls back to the preset and no scalePercent is reported, rather than a paper code the file itself disowns being resolved into a confident page size. And WsBool's own fFitToPage decides which of Setup's two mutually exclusive scaling fields is live: iFitWidth/iFitHeight when set, iScale when clear. Real producers write both regardless (confirmed against LibreOffice-written BIFF8, which carries iScale=100 alongside a real fit-to-page pair, and a real iScale alongside iFitWidth=iFitHeight=1), so reading both would report a scale and a page count that contradict each other.
|
|
47
|
+
*/
|
|
48
|
+
function mapPrintSettings(raw, names) {
|
|
49
|
+
const setup = raw.setup;
|
|
50
|
+
const usable = setup !== void 0 && !setup.noPls;
|
|
51
|
+
const settings = {
|
|
52
|
+
pageSize: (usable ? require_biff_print_setup.pageSizeFromSetup(setup) : void 0) ?? DEFAULT_PRINT_SETTINGS.pageSize,
|
|
53
|
+
margins: {
|
|
54
|
+
topPt: raw.marginsPt.top ?? DEFAULT_PRINT_SETTINGS.margins.topPt,
|
|
55
|
+
rightPt: raw.marginsPt.right ?? DEFAULT_PRINT_SETTINGS.margins.rightPt,
|
|
56
|
+
bottomPt: raw.marginsPt.bottom ?? DEFAULT_PRINT_SETTINGS.margins.bottomPt,
|
|
57
|
+
leftPt: raw.marginsPt.left ?? DEFAULT_PRINT_SETTINGS.margins.leftPt
|
|
58
|
+
},
|
|
59
|
+
gridlines: raw.printGridlines ?? DEFAULT_PRINT_SETTINGS.gridlines,
|
|
60
|
+
headers: raw.printHeaders ?? DEFAULT_PRINT_SETTINGS.headers,
|
|
61
|
+
pageOrder: setup?.leftToRight === true ? "overThenDown" : "downThenOver"
|
|
62
|
+
};
|
|
63
|
+
if (setup !== void 0 && raw.fitToPage === true) {
|
|
64
|
+
if (setup.fitWidth > 0 && setup.fitHeight > 0) settings.fitToPages = {
|
|
65
|
+
width: setup.fitWidth,
|
|
66
|
+
height: setup.fitHeight
|
|
67
|
+
};
|
|
68
|
+
} else if (usable && setup.scalePercent > 0 && setup.scalePercent !== ACTUAL_SIZE_SCALE_PERCENT) settings.scalePercent = setup.scalePercent;
|
|
69
|
+
if (raw.rowBreaks.length > 0 || raw.columnBreaks.length > 0) settings.manualBreaks = {
|
|
70
|
+
rows: [...raw.rowBreaks],
|
|
71
|
+
columns: [...raw.columnBreaks]
|
|
72
|
+
};
|
|
73
|
+
if (names?.printRange !== void 0) settings.printRange = names.printRange;
|
|
74
|
+
if (names?.repeatRows !== void 0) settings.repeatRows = names.repeatRows;
|
|
75
|
+
if (names?.repeatColumns !== void 0) settings.repeatColumns = names.repeatColumns;
|
|
76
|
+
return settings;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
33
79
|
* Reads a .xls file's bytes into a ContentDocument.
|
|
34
80
|
*
|
|
35
81
|
* The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format.
|
|
36
82
|
*/
|
|
37
83
|
function readXlsContent(bytes) {
|
|
38
|
-
const
|
|
84
|
+
const { workbook, metadata } = require_container.readWorkbookStreams(bytes);
|
|
85
|
+
const substreams = require_biff_substreams.splitSubstreams(require_biff_substreams.groupRecords(require_biff_records.readRecords(workbook)));
|
|
39
86
|
const globalsSubstream = substreams[0];
|
|
40
87
|
if (globalsSubstream === void 0) throw new require_biff_records.BiffFormatError("workbook stream holds no substreams, so it carries no globals substream");
|
|
41
88
|
if (globalsSubstream.records.some((rec) => rec.type === 47)) throw new require_biff_records.BiffFormatError("workbook is encrypted (its globals substream carries a FilePass record); this reader does not decrypt");
|
|
42
89
|
const globals = require_workbook_globals.readWorkbookGlobals(globalsSubstream.records);
|
|
90
|
+
const sheets = globals.sheets.map((entry, sheetIndex) => ({
|
|
91
|
+
entry,
|
|
92
|
+
sheetIndex
|
|
93
|
+
})).filter(({ entry }) => entry.sheetType === SHEET_TYPE_WORKSHEET).map(({ entry, sheetIndex }) => readSheet(entry, sheetIndex, substreams, globals));
|
|
43
94
|
return {
|
|
44
95
|
kind: "spreadsheet",
|
|
45
|
-
metadata: {},
|
|
46
|
-
sheets
|
|
96
|
+
metadata: metadata === void 0 ? {} : (0, archive_codec.summaryInformationToLayoutMetadata)((0, archive_codec.readSummaryInformation)(metadata)),
|
|
97
|
+
sheets
|
|
47
98
|
};
|
|
48
99
|
}
|
|
49
100
|
/** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
|
|
@@ -55,21 +106,29 @@ function readXls(bytes) {
|
|
|
55
106
|
*
|
|
56
107
|
* The substream is found by the byte offset BoundSheet8's lbPlyPos names, not by position: the order sheets appear in the workbook (which is BoundSheet8 order, and therefore the order of `globals.sheets`) is not required to match the order their substreams were written in. A sheet whose substream cannot be found still produces a ContentSheet, empty -- losing the sheet entirely would be a worse answer than losing its cells, since its name and position are real information the workbook did state.
|
|
57
108
|
*/
|
|
58
|
-
function readSheet(entry, substreams, globals) {
|
|
109
|
+
function readSheet(entry, sheetIndex, substreams, globals) {
|
|
59
110
|
const substream = substreams.find((candidate) => candidate.offset === entry.bofPosition && candidate.documentType === 16);
|
|
60
111
|
const raw = substream === void 0 ? {
|
|
61
112
|
cells: [],
|
|
62
113
|
rows: [],
|
|
63
114
|
columns: [],
|
|
64
|
-
merges: []
|
|
65
|
-
|
|
115
|
+
merges: [],
|
|
116
|
+
print: {
|
|
117
|
+
marginsPt: {},
|
|
118
|
+
rowBreaks: [],
|
|
119
|
+
columnBreaks: []
|
|
120
|
+
}
|
|
121
|
+
} : require_workbook_sheet.readSheetRecords(substream.records, globals.sharedStrings, {
|
|
122
|
+
sheets: globals.sheets,
|
|
123
|
+
sheetRanges: globals.sheetRanges
|
|
124
|
+
});
|
|
66
125
|
return {
|
|
67
126
|
name: entry.name,
|
|
68
127
|
cells: mapCells(raw, globals),
|
|
69
128
|
columns: mapColumns(raw),
|
|
70
129
|
rows: mapRows(raw),
|
|
71
130
|
images: [],
|
|
72
|
-
printSettings:
|
|
131
|
+
printSettings: mapPrintSettings(raw.print, globals.printNames.get(sheetIndex))
|
|
73
132
|
};
|
|
74
133
|
}
|
|
75
134
|
function mapRows(raw) {
|
|
@@ -107,10 +166,15 @@ function mapCells(raw, globals) {
|
|
|
107
166
|
/**
|
|
108
167
|
* Maps one raw cell, or drops it.
|
|
109
168
|
*
|
|
110
|
-
* A blank cell
|
|
169
|
+
* A blank cell showing nothing at all is dropped: ContentSheet's cell array is documented as sparse, holding only cells with something to show, and dropping the blanks keeps it honest rather than filling a sheet with thousands of empty entries -- applyMerges below re-materialises the few that anchor a merged range.
|
|
170
|
+
*
|
|
171
|
+
* A Blank or MulBlank record whose own XF carries a background or a border is not that case. Its formatting is the entire reason the record exists -- a producer writes one precisely to say "this cell is empty AND looks like this" -- so it becomes an `empty`-kind cell carrying that decoration, which is also what this package's own writer emits for one.
|
|
111
172
|
*/
|
|
112
173
|
function mapCell(cell, globals) {
|
|
113
|
-
|
|
174
|
+
const background = backgroundOf(globals, cell.xfIndex);
|
|
175
|
+
const borders = bordersOf(globals, cell.xfIndex);
|
|
176
|
+
const { alignment, verticalAlignment } = alignmentOf(globals, cell.xfIndex);
|
|
177
|
+
if (cell.value.kind === "blank" && background === void 0 && borders === void 0 && alignment === void 0 && verticalAlignment === void 0) return;
|
|
114
178
|
const formatCode = require_workbook_globals.formatCodeOf(globals, cell.xfIndex);
|
|
115
179
|
const value = resolveValue(cell, formatCode, globals.date1904);
|
|
116
180
|
const mapped = {
|
|
@@ -120,9 +184,50 @@ function mapCell(cell, globals) {
|
|
|
120
184
|
displayText: displayTextOf(value)
|
|
121
185
|
};
|
|
122
186
|
if (formatCode !== void 0) mapped.numberFormatCode = formatCode;
|
|
187
|
+
if (cell.formula !== void 0) mapped.formula = cell.formula;
|
|
188
|
+
if (background !== void 0) mapped.background = background;
|
|
189
|
+
if (borders !== void 0) mapped.borders = borders;
|
|
190
|
+
if (alignment !== void 0) mapped.alignment = alignment;
|
|
191
|
+
if (verticalAlignment !== void 0) mapped.verticalAlignment = verticalAlignment;
|
|
123
192
|
return mapped;
|
|
124
193
|
}
|
|
125
194
|
/**
|
|
195
|
+
* A cell's own resolved fill colour, or undefined for a genuinely unfilled cell AND for every fill pattern beyond solid.
|
|
196
|
+
*
|
|
197
|
+
* A non-solid pattern (the 50%/75%/25% gray shades, the stripe and crosshatch family [MS-XLS]'s FillPattern enumeration also names) is a deliberate, permanent gap rather than an oversight: ContentSheetCell.background models one flat colour, and approximating a striped or crosshatched fill as its own foreground colour alone would misreport what the cell actually shows -- see xls-codec's README, "Cell decoration".
|
|
198
|
+
*/
|
|
199
|
+
function backgroundOf(globals, xfIndex) {
|
|
200
|
+
const format = globals.cellFormats[xfIndex];
|
|
201
|
+
if (format === void 0) return;
|
|
202
|
+
return require_biff_xf_colors.resolveFillBackground(format.decoration.fillPattern, format.decoration.fillForegroundIcv, globals.palette);
|
|
203
|
+
}
|
|
204
|
+
/** A cell's own resolved horizontal/vertical alignment -- already the exact Alignment/verticalAlignment members (or undefined) globals.ts's readCellFormat resolved through xf-colors.ts's unpackXfAlignment, so this is a lookup rather than a further resolution step, mirroring backgroundOf/bordersOf's own shape. Both fields undefined for a cell whose XF resolves to no CellFormat at all (an out-of-range xfIndex), matching every other resolveXOf helper's behaviour in that case. */
|
|
205
|
+
function alignmentOf(globals, xfIndex) {
|
|
206
|
+
const format = globals.cellFormats[xfIndex];
|
|
207
|
+
if (format === void 0) return {};
|
|
208
|
+
const result = {};
|
|
209
|
+
if (format.alignment.horizontal !== void 0) result.alignment = format.alignment.horizontal;
|
|
210
|
+
if (format.alignment.vertical !== void 0) result.verticalAlignment = format.alignment.vertical;
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
/** A cell's own resolved per-side borders, or undefined when none of its four sides carry a border this reader resolves (no border at all, or a reserved/unrecognised BorderStyle token, or a colour this package cannot express as a fixed RGB value -- see xf-colors.ts's own resolveBorderEdge). */
|
|
214
|
+
function bordersOf(globals, xfIndex) {
|
|
215
|
+
const format = globals.cellFormats[xfIndex];
|
|
216
|
+
if (format === void 0) return;
|
|
217
|
+
const { decoration } = format;
|
|
218
|
+
const left = require_biff_xf_colors.resolveBorderEdge(decoration.left, globals.palette);
|
|
219
|
+
const right = require_biff_xf_colors.resolveBorderEdge(decoration.right, globals.palette);
|
|
220
|
+
const top = require_biff_xf_colors.resolveBorderEdge(decoration.top, globals.palette);
|
|
221
|
+
const bottom = require_biff_xf_colors.resolveBorderEdge(decoration.bottom, globals.palette);
|
|
222
|
+
if (left === void 0 && right === void 0 && top === void 0 && bottom === void 0) return;
|
|
223
|
+
const borders = {};
|
|
224
|
+
if (left !== void 0) borders.left = left;
|
|
225
|
+
if (right !== void 0) borders.right = right;
|
|
226
|
+
if (top !== void 0) borders.top = top;
|
|
227
|
+
if (bottom !== void 0) borders.bottom = bottom;
|
|
228
|
+
return borders;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
126
231
|
* Resolves a raw value into a ContentCellValue, classifying a number through its own format code.
|
|
127
232
|
*
|
|
128
233
|
* This is where BIFF8's lack of temporal and percentage cell types is undone: every date, time, percentage, and currency amount is stored as a bare number, and only the format its XF points at says which. A format naming a date the calendar does not have (the 1900 system's phantom leap day, or a negative serial) degrades to the plain number rather than emitting an invalid ISO string.
|
|
@@ -135,7 +240,7 @@ function resolveValue(cell, formatCode, date1904) {
|
|
|
135
240
|
kind: "number",
|
|
136
241
|
value: num
|
|
137
242
|
};
|
|
138
|
-
const format =
|
|
243
|
+
const format = (0, excel_number_format.classifyNumberFormat)(formatCode);
|
|
139
244
|
switch (format.kind) {
|
|
140
245
|
case "percentage": return {
|
|
141
246
|
kind: "percentage",
|
package/dist/content.js
CHANGED
|
@@ -1,48 +1,99 @@
|
|
|
1
1
|
import "./biff/record-types.js";
|
|
2
2
|
import { BiffFormatError, readRecords } from "./biff/records.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readWorkbookStreams } from "./container.js";
|
|
4
|
+
import { inchesToPoints } from "./units.js";
|
|
5
|
+
import { pageSizeFromSetup } from "./biff/print-setup.js";
|
|
4
6
|
import { groupRecords, splitSubstreams } from "./biff/substreams.js";
|
|
5
|
-
import {
|
|
7
|
+
import { resolveBorderEdge, resolveFillBackground } from "./biff/xf-colors.js";
|
|
6
8
|
import { serialToIsoDate, serialToIsoDateTime, serialToIsoTime } from "./serial.js";
|
|
7
9
|
import { formatCodeOf, readWorkbookGlobals } from "./workbook/globals.js";
|
|
8
10
|
import { readSheetRecords } from "./workbook/sheet.js";
|
|
11
|
+
import { readSummaryInformation, summaryInformationToLayoutMetadata } from "archive-codec";
|
|
9
12
|
import { PAGE_SIZE_LETTER, assembleTree } from "document-schema.js";
|
|
13
|
+
import { classifyNumberFormat } from "excel-number-format";
|
|
10
14
|
//#region src/content.ts
|
|
11
15
|
/** Which BoundSheet8 dt values name a sheet this reader maps. 0x00 is a worksheet or dialog sheet; macro sheets, chart sheets, and VBA modules carry no cell table for ContentSheet to hold. */
|
|
12
16
|
const SHEET_TYPE_WORKSHEET = 0;
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
18
|
+
* Excel's own "Normal" page-setup preset, the per-field fallback for a print setting the file states nothing about.
|
|
15
19
|
*
|
|
16
|
-
* ContentSheetPrintSettings makes pageSize, margins, gridlines, headers, and pageOrder REQUIRED,
|
|
20
|
+
* ContentSheetPrintSettings makes pageSize, margins, gridlines, headers, and pageOrder REQUIRED, while [MS-XLS] 2.1.7.20.6's own PAGESETUP production makes every record behind them optional -- so a sheet whose page setup was never touched genuinely carries no Setup and no margin records, and something has to stand in. These are the values Excel itself calls Normal (top/bottom 0.75in, left/right 0.7in, on Letter paper, gridlines and row/column headers not printed, pages down-then-over), and the identical constants ooxml.js falls back to for an xlsx carrying no pageMargins element -- so the same untouched sheet reads the same either way.
|
|
21
|
+
*
|
|
22
|
+
* Each field falls back independently: a sheet that declares a left margin and nothing else keeps its real left margin and takes the preset for the other three, rather than the whole preset displacing the one value the file actually stated.
|
|
17
23
|
*/
|
|
18
|
-
const POINTS_PER_INCH = 72;
|
|
19
24
|
const DEFAULT_PRINT_SETTINGS = {
|
|
20
25
|
pageSize: PAGE_SIZE_LETTER,
|
|
21
26
|
margins: {
|
|
22
|
-
topPt: .75
|
|
23
|
-
rightPt: .7
|
|
24
|
-
bottomPt: .75
|
|
25
|
-
leftPt: .7
|
|
27
|
+
topPt: inchesToPoints(.75),
|
|
28
|
+
rightPt: inchesToPoints(.7),
|
|
29
|
+
bottomPt: inchesToPoints(.75),
|
|
30
|
+
leftPt: inchesToPoints(.7)
|
|
26
31
|
},
|
|
27
32
|
gridlines: false,
|
|
28
33
|
headers: false,
|
|
29
34
|
pageOrder: "downThenOver"
|
|
30
35
|
};
|
|
31
36
|
/**
|
|
37
|
+
* The iScale value that says "print at actual size", which is what ContentSheetPrintSettings already means by carrying no scalePercent at all.
|
|
38
|
+
*
|
|
39
|
+
* Setup's own iScale is a mandatory field of a mandatory record, with no spelling for "this sheet declares no scale" -- so an untouched sheet still states 100. Reporting that as an explicit scalePercent would put a field on every sheet of every workbook read, carrying nothing a consumer could act on that its absence does not already say, and would mean a document written with no scale came back with one. The two spellings render identically, so collapsing them onto the absent one is lossless in both directions; a scale that is genuinely anything else is reported exactly as the file states it.
|
|
40
|
+
*/
|
|
41
|
+
const ACTUAL_SIZE_SCALE_PERCENT = 100;
|
|
42
|
+
/**
|
|
43
|
+
* The print settings a sheet's own records and its built-in print names state, with the Normal preset filling in what they do not.
|
|
44
|
+
*
|
|
45
|
+
* Two of BIFF8's own conditional rules are honoured rather than flattened. A Setup record whose fNoPls bit is set declares its own paper size and scale undefined ([MS-XLS] 2.4.257: "whether the iPaperSize, iScale, iRes, iVRes, iCopies, fNoOrient, and fPortrait data are undefined and ignored"), so neither is read from it -- the page size falls back to the preset and no scalePercent is reported, rather than a paper code the file itself disowns being resolved into a confident page size. And WsBool's own fFitToPage decides which of Setup's two mutually exclusive scaling fields is live: iFitWidth/iFitHeight when set, iScale when clear. Real producers write both regardless (confirmed against LibreOffice-written BIFF8, which carries iScale=100 alongside a real fit-to-page pair, and a real iScale alongside iFitWidth=iFitHeight=1), so reading both would report a scale and a page count that contradict each other.
|
|
46
|
+
*/
|
|
47
|
+
function mapPrintSettings(raw, names) {
|
|
48
|
+
const setup = raw.setup;
|
|
49
|
+
const usable = setup !== void 0 && !setup.noPls;
|
|
50
|
+
const settings = {
|
|
51
|
+
pageSize: (usable ? pageSizeFromSetup(setup) : void 0) ?? DEFAULT_PRINT_SETTINGS.pageSize,
|
|
52
|
+
margins: {
|
|
53
|
+
topPt: raw.marginsPt.top ?? DEFAULT_PRINT_SETTINGS.margins.topPt,
|
|
54
|
+
rightPt: raw.marginsPt.right ?? DEFAULT_PRINT_SETTINGS.margins.rightPt,
|
|
55
|
+
bottomPt: raw.marginsPt.bottom ?? DEFAULT_PRINT_SETTINGS.margins.bottomPt,
|
|
56
|
+
leftPt: raw.marginsPt.left ?? DEFAULT_PRINT_SETTINGS.margins.leftPt
|
|
57
|
+
},
|
|
58
|
+
gridlines: raw.printGridlines ?? DEFAULT_PRINT_SETTINGS.gridlines,
|
|
59
|
+
headers: raw.printHeaders ?? DEFAULT_PRINT_SETTINGS.headers,
|
|
60
|
+
pageOrder: setup?.leftToRight === true ? "overThenDown" : "downThenOver"
|
|
61
|
+
};
|
|
62
|
+
if (setup !== void 0 && raw.fitToPage === true) {
|
|
63
|
+
if (setup.fitWidth > 0 && setup.fitHeight > 0) settings.fitToPages = {
|
|
64
|
+
width: setup.fitWidth,
|
|
65
|
+
height: setup.fitHeight
|
|
66
|
+
};
|
|
67
|
+
} else if (usable && setup.scalePercent > 0 && setup.scalePercent !== ACTUAL_SIZE_SCALE_PERCENT) settings.scalePercent = setup.scalePercent;
|
|
68
|
+
if (raw.rowBreaks.length > 0 || raw.columnBreaks.length > 0) settings.manualBreaks = {
|
|
69
|
+
rows: [...raw.rowBreaks],
|
|
70
|
+
columns: [...raw.columnBreaks]
|
|
71
|
+
};
|
|
72
|
+
if (names?.printRange !== void 0) settings.printRange = names.printRange;
|
|
73
|
+
if (names?.repeatRows !== void 0) settings.repeatRows = names.repeatRows;
|
|
74
|
+
if (names?.repeatColumns !== void 0) settings.repeatColumns = names.repeatColumns;
|
|
75
|
+
return settings;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
32
78
|
* Reads a .xls file's bytes into a ContentDocument.
|
|
33
79
|
*
|
|
34
80
|
* The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format.
|
|
35
81
|
*/
|
|
36
82
|
function readXlsContent(bytes) {
|
|
37
|
-
const
|
|
83
|
+
const { workbook, metadata } = readWorkbookStreams(bytes);
|
|
84
|
+
const substreams = splitSubstreams(groupRecords(readRecords(workbook)));
|
|
38
85
|
const globalsSubstream = substreams[0];
|
|
39
86
|
if (globalsSubstream === void 0) throw new BiffFormatError("workbook stream holds no substreams, so it carries no globals substream");
|
|
40
87
|
if (globalsSubstream.records.some((rec) => rec.type === 47)) throw new BiffFormatError("workbook is encrypted (its globals substream carries a FilePass record); this reader does not decrypt");
|
|
41
88
|
const globals = readWorkbookGlobals(globalsSubstream.records);
|
|
89
|
+
const sheets = globals.sheets.map((entry, sheetIndex) => ({
|
|
90
|
+
entry,
|
|
91
|
+
sheetIndex
|
|
92
|
+
})).filter(({ entry }) => entry.sheetType === SHEET_TYPE_WORKSHEET).map(({ entry, sheetIndex }) => readSheet(entry, sheetIndex, substreams, globals));
|
|
42
93
|
return {
|
|
43
94
|
kind: "spreadsheet",
|
|
44
|
-
metadata: {},
|
|
45
|
-
sheets
|
|
95
|
+
metadata: metadata === void 0 ? {} : summaryInformationToLayoutMetadata(readSummaryInformation(metadata)),
|
|
96
|
+
sheets
|
|
46
97
|
};
|
|
47
98
|
}
|
|
48
99
|
/** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
|
|
@@ -54,21 +105,29 @@ function readXls(bytes) {
|
|
|
54
105
|
*
|
|
55
106
|
* The substream is found by the byte offset BoundSheet8's lbPlyPos names, not by position: the order sheets appear in the workbook (which is BoundSheet8 order, and therefore the order of `globals.sheets`) is not required to match the order their substreams were written in. A sheet whose substream cannot be found still produces a ContentSheet, empty -- losing the sheet entirely would be a worse answer than losing its cells, since its name and position are real information the workbook did state.
|
|
56
107
|
*/
|
|
57
|
-
function readSheet(entry, substreams, globals) {
|
|
108
|
+
function readSheet(entry, sheetIndex, substreams, globals) {
|
|
58
109
|
const substream = substreams.find((candidate) => candidate.offset === entry.bofPosition && candidate.documentType === 16);
|
|
59
110
|
const raw = substream === void 0 ? {
|
|
60
111
|
cells: [],
|
|
61
112
|
rows: [],
|
|
62
113
|
columns: [],
|
|
63
|
-
merges: []
|
|
64
|
-
|
|
114
|
+
merges: [],
|
|
115
|
+
print: {
|
|
116
|
+
marginsPt: {},
|
|
117
|
+
rowBreaks: [],
|
|
118
|
+
columnBreaks: []
|
|
119
|
+
}
|
|
120
|
+
} : readSheetRecords(substream.records, globals.sharedStrings, {
|
|
121
|
+
sheets: globals.sheets,
|
|
122
|
+
sheetRanges: globals.sheetRanges
|
|
123
|
+
});
|
|
65
124
|
return {
|
|
66
125
|
name: entry.name,
|
|
67
126
|
cells: mapCells(raw, globals),
|
|
68
127
|
columns: mapColumns(raw),
|
|
69
128
|
rows: mapRows(raw),
|
|
70
129
|
images: [],
|
|
71
|
-
printSettings:
|
|
130
|
+
printSettings: mapPrintSettings(raw.print, globals.printNames.get(sheetIndex))
|
|
72
131
|
};
|
|
73
132
|
}
|
|
74
133
|
function mapRows(raw) {
|
|
@@ -106,10 +165,15 @@ function mapCells(raw, globals) {
|
|
|
106
165
|
/**
|
|
107
166
|
* Maps one raw cell, or drops it.
|
|
108
167
|
*
|
|
109
|
-
* A blank cell
|
|
168
|
+
* A blank cell showing nothing at all is dropped: ContentSheet's cell array is documented as sparse, holding only cells with something to show, and dropping the blanks keeps it honest rather than filling a sheet with thousands of empty entries -- applyMerges below re-materialises the few that anchor a merged range.
|
|
169
|
+
*
|
|
170
|
+
* A Blank or MulBlank record whose own XF carries a background or a border is not that case. Its formatting is the entire reason the record exists -- a producer writes one precisely to say "this cell is empty AND looks like this" -- so it becomes an `empty`-kind cell carrying that decoration, which is also what this package's own writer emits for one.
|
|
110
171
|
*/
|
|
111
172
|
function mapCell(cell, globals) {
|
|
112
|
-
|
|
173
|
+
const background = backgroundOf(globals, cell.xfIndex);
|
|
174
|
+
const borders = bordersOf(globals, cell.xfIndex);
|
|
175
|
+
const { alignment, verticalAlignment } = alignmentOf(globals, cell.xfIndex);
|
|
176
|
+
if (cell.value.kind === "blank" && background === void 0 && borders === void 0 && alignment === void 0 && verticalAlignment === void 0) return;
|
|
113
177
|
const formatCode = formatCodeOf(globals, cell.xfIndex);
|
|
114
178
|
const value = resolveValue(cell, formatCode, globals.date1904);
|
|
115
179
|
const mapped = {
|
|
@@ -119,9 +183,50 @@ function mapCell(cell, globals) {
|
|
|
119
183
|
displayText: displayTextOf(value)
|
|
120
184
|
};
|
|
121
185
|
if (formatCode !== void 0) mapped.numberFormatCode = formatCode;
|
|
186
|
+
if (cell.formula !== void 0) mapped.formula = cell.formula;
|
|
187
|
+
if (background !== void 0) mapped.background = background;
|
|
188
|
+
if (borders !== void 0) mapped.borders = borders;
|
|
189
|
+
if (alignment !== void 0) mapped.alignment = alignment;
|
|
190
|
+
if (verticalAlignment !== void 0) mapped.verticalAlignment = verticalAlignment;
|
|
122
191
|
return mapped;
|
|
123
192
|
}
|
|
124
193
|
/**
|
|
194
|
+
* A cell's own resolved fill colour, or undefined for a genuinely unfilled cell AND for every fill pattern beyond solid.
|
|
195
|
+
*
|
|
196
|
+
* A non-solid pattern (the 50%/75%/25% gray shades, the stripe and crosshatch family [MS-XLS]'s FillPattern enumeration also names) is a deliberate, permanent gap rather than an oversight: ContentSheetCell.background models one flat colour, and approximating a striped or crosshatched fill as its own foreground colour alone would misreport what the cell actually shows -- see xls-codec's README, "Cell decoration".
|
|
197
|
+
*/
|
|
198
|
+
function backgroundOf(globals, xfIndex) {
|
|
199
|
+
const format = globals.cellFormats[xfIndex];
|
|
200
|
+
if (format === void 0) return;
|
|
201
|
+
return resolveFillBackground(format.decoration.fillPattern, format.decoration.fillForegroundIcv, globals.palette);
|
|
202
|
+
}
|
|
203
|
+
/** A cell's own resolved horizontal/vertical alignment -- already the exact Alignment/verticalAlignment members (or undefined) globals.ts's readCellFormat resolved through xf-colors.ts's unpackXfAlignment, so this is a lookup rather than a further resolution step, mirroring backgroundOf/bordersOf's own shape. Both fields undefined for a cell whose XF resolves to no CellFormat at all (an out-of-range xfIndex), matching every other resolveXOf helper's behaviour in that case. */
|
|
204
|
+
function alignmentOf(globals, xfIndex) {
|
|
205
|
+
const format = globals.cellFormats[xfIndex];
|
|
206
|
+
if (format === void 0) return {};
|
|
207
|
+
const result = {};
|
|
208
|
+
if (format.alignment.horizontal !== void 0) result.alignment = format.alignment.horizontal;
|
|
209
|
+
if (format.alignment.vertical !== void 0) result.verticalAlignment = format.alignment.vertical;
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
/** A cell's own resolved per-side borders, or undefined when none of its four sides carry a border this reader resolves (no border at all, or a reserved/unrecognised BorderStyle token, or a colour this package cannot express as a fixed RGB value -- see xf-colors.ts's own resolveBorderEdge). */
|
|
213
|
+
function bordersOf(globals, xfIndex) {
|
|
214
|
+
const format = globals.cellFormats[xfIndex];
|
|
215
|
+
if (format === void 0) return;
|
|
216
|
+
const { decoration } = format;
|
|
217
|
+
const left = resolveBorderEdge(decoration.left, globals.palette);
|
|
218
|
+
const right = resolveBorderEdge(decoration.right, globals.palette);
|
|
219
|
+
const top = resolveBorderEdge(decoration.top, globals.palette);
|
|
220
|
+
const bottom = resolveBorderEdge(decoration.bottom, globals.palette);
|
|
221
|
+
if (left === void 0 && right === void 0 && top === void 0 && bottom === void 0) return;
|
|
222
|
+
const borders = {};
|
|
223
|
+
if (left !== void 0) borders.left = left;
|
|
224
|
+
if (right !== void 0) borders.right = right;
|
|
225
|
+
if (top !== void 0) borders.top = top;
|
|
226
|
+
if (bottom !== void 0) borders.bottom = bottom;
|
|
227
|
+
return borders;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
125
230
|
* Resolves a raw value into a ContentCellValue, classifying a number through its own format code.
|
|
126
231
|
*
|
|
127
232
|
* This is where BIFF8's lack of temporal and percentage cell types is undone: every date, time, percentage, and currency amount is stored as a bare number, and only the format its XF points at says which. A format naming a date the calendar does not have (the 1900 system's phantom leap day, or a negative serial) degrades to the plain number rather than emitting an invalid ISO string.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/biff/cursor.d.ts
|
|
2
|
+
declare class BlockCursor {
|
|
3
|
+
private readonly blocks;
|
|
4
|
+
private blockIndex;
|
|
5
|
+
private offset;
|
|
6
|
+
constructor(blocks: readonly Uint8Array<ArrayBuffer>[]);
|
|
7
|
+
/** Advances past any exhausted or empty blocks, so the cursor always rests either on a readable byte or past the end of the last block. A Continue carrying no data is legal and must not read as the end of the record. */
|
|
8
|
+
private settle;
|
|
9
|
+
private nextByte;
|
|
10
|
+
/** Whether any unread byte remains, in this block or a later one. */
|
|
11
|
+
hasMore(): boolean;
|
|
12
|
+
/** How many unread bytes remain in the block the cursor currently rests in -- the distance to the next continuation boundary, which a string's character run must not cross without consuming a new flag byte. */
|
|
13
|
+
remainingInBlock(): number;
|
|
14
|
+
/** The index of the block the cursor currently rests in, so a caller can detect that a read crossed into a new one. */
|
|
15
|
+
blockPosition(): number;
|
|
16
|
+
u8(): number;
|
|
17
|
+
u16(): number;
|
|
18
|
+
u32(): number;
|
|
19
|
+
i32(): number;
|
|
20
|
+
/** A signed 16-bit integer, sign-extended by shifting the raw value out of and back into the low 16 bits -- what XTI's itabFirst/itabLast ([MS-XLS] 2.5.344) and a handful of other structures carry. */
|
|
21
|
+
i16(): number;
|
|
22
|
+
/** An Xnum ([MS-XLS] 2.5.342): a little-endian IEEE 754 double. */
|
|
23
|
+
f64(): number;
|
|
24
|
+
/** The next `count` bytes, copied out. Spans block boundaries. */
|
|
25
|
+
take(count: number): Uint8Array<ArrayBuffer>;
|
|
26
|
+
/** Advances `count` bytes without materialising them -- for a field this package reads past rather than reads. */
|
|
27
|
+
skip(count: number): void;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { BlockCursor as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/biff/cursor.d.ts
|
|
2
|
+
declare class BlockCursor {
|
|
3
|
+
private readonly blocks;
|
|
4
|
+
private blockIndex;
|
|
5
|
+
private offset;
|
|
6
|
+
constructor(blocks: readonly Uint8Array<ArrayBuffer>[]);
|
|
7
|
+
/** Advances past any exhausted or empty blocks, so the cursor always rests either on a readable byte or past the end of the last block. A Continue carrying no data is legal and must not read as the end of the record. */
|
|
8
|
+
private settle;
|
|
9
|
+
private nextByte;
|
|
10
|
+
/** Whether any unread byte remains, in this block or a later one. */
|
|
11
|
+
hasMore(): boolean;
|
|
12
|
+
/** How many unread bytes remain in the block the cursor currently rests in -- the distance to the next continuation boundary, which a string's character run must not cross without consuming a new flag byte. */
|
|
13
|
+
remainingInBlock(): number;
|
|
14
|
+
/** The index of the block the cursor currently rests in, so a caller can detect that a read crossed into a new one. */
|
|
15
|
+
blockPosition(): number;
|
|
16
|
+
u8(): number;
|
|
17
|
+
u16(): number;
|
|
18
|
+
u32(): number;
|
|
19
|
+
i32(): number;
|
|
20
|
+
/** A signed 16-bit integer, sign-extended by shifting the raw value out of and back into the low 16 bits -- what XTI's itabFirst/itabLast ([MS-XLS] 2.5.344) and a handful of other structures carry. */
|
|
21
|
+
i16(): number;
|
|
22
|
+
/** An Xnum ([MS-XLS] 2.5.342): a little-endian IEEE 754 double. */
|
|
23
|
+
f64(): number;
|
|
24
|
+
/** The next `count` bytes, copied out. Spans block boundaries. */
|
|
25
|
+
take(count: number): Uint8Array<ArrayBuffer>;
|
|
26
|
+
/** Advances `count` bytes without materialising them -- for a field this package reads past rather than reads. */
|
|
27
|
+
skip(count: number): void;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { BlockCursor as t };
|