xls-codec 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/dist/biff/cursor.cjs +73 -0
  4. package/dist/biff/cursor.d.cts +28 -0
  5. package/dist/biff/cursor.d.ts +28 -0
  6. package/dist/biff/cursor.js +72 -0
  7. package/dist/biff/errors.cjs +18 -0
  8. package/dist/biff/errors.d.cts +5 -0
  9. package/dist/biff/errors.d.ts +5 -0
  10. package/dist/biff/errors.js +17 -0
  11. package/dist/biff/record-types.cjs +108 -0
  12. package/dist/biff/record-types.d.cts +73 -0
  13. package/dist/biff/record-types.d.ts +73 -0
  14. package/dist/biff/record-types.js +73 -0
  15. package/dist/biff/records.cjs +40 -0
  16. package/dist/biff/records.d.cts +2 -0
  17. package/dist/biff/records.d.ts +2 -0
  18. package/dist/biff/records.js +38 -0
  19. package/dist/biff/rk.cjs +29 -0
  20. package/dist/biff/rk.d.cts +5 -0
  21. package/dist/biff/rk.d.ts +5 -0
  22. package/dist/biff/rk.js +28 -0
  23. package/dist/biff/strings.cjs +66 -0
  24. package/dist/biff/strings.d.cts +14 -0
  25. package/dist/biff/strings.d.ts +14 -0
  26. package/dist/biff/strings.js +63 -0
  27. package/dist/biff/substreams.cjs +71 -0
  28. package/dist/biff/substreams.d.cts +21 -0
  29. package/dist/biff/substreams.d.ts +21 -0
  30. package/dist/biff/substreams.js +69 -0
  31. package/dist/container.cjs +46 -0
  32. package/dist/container.d.cts +15 -0
  33. package/dist/container.d.ts +15 -0
  34. package/dist/container.js +44 -0
  35. package/dist/content.cjs +230 -0
  36. package/dist/content.d.cts +20 -0
  37. package/dist/content.d.ts +20 -0
  38. package/dist/content.js +228 -0
  39. package/dist/index.cjs +74 -0
  40. package/dist/index.d.cts +15 -0
  41. package/dist/index.d.ts +15 -0
  42. package/dist/index.js +15 -0
  43. package/dist/number-format.cjs +298 -0
  44. package/dist/number-format.d.cts +32 -0
  45. package/dist/number-format.d.ts +32 -0
  46. package/dist/number-format.js +296 -0
  47. package/dist/records-DVIqXFKk.d.cts +20 -0
  48. package/dist/records-DVIqXFKk.d.ts +20 -0
  49. package/dist/serial.cjs +62 -0
  50. package/dist/serial.d.cts +7 -0
  51. package/dist/serial.d.ts +7 -0
  52. package/dist/serial.js +59 -0
  53. package/dist/units.cjs +30 -0
  54. package/dist/units.d.cts +11 -0
  55. package/dist/units.d.ts +11 -0
  56. package/dist/units.js +28 -0
  57. package/dist/workbook/globals.cjs +106 -0
  58. package/dist/workbook/globals.d.cts +45 -0
  59. package/dist/workbook/globals.d.ts +45 -0
  60. package/dist/workbook/globals.js +104 -0
  61. package/dist/workbook/sheet.cjs +372 -0
  62. package/dist/workbook/sheet.d.cts +58 -0
  63. package/dist/workbook/sheet.d.ts +58 -0
  64. package/dist/workbook/sheet.js +371 -0
  65. package/package.json +85 -2
@@ -0,0 +1,69 @@
1
+ import "./record-types.js";
2
+ import { BiffFormatError } from "./records.js";
3
+ //#region src/biff/substreams.ts
4
+ /** Joins each record to the Continue records following it, keeping the blocks separate. */
5
+ function groupRecords(records) {
6
+ const groups = [];
7
+ for (const record of records) {
8
+ if (record.type === 60) {
9
+ const current = groups[groups.length - 1];
10
+ if (current === void 0) throw new BiffFormatError("Continue record with no preceding record to continue");
11
+ current.blocks.push(record.data);
12
+ continue;
13
+ }
14
+ groups.push({
15
+ type: record.type,
16
+ blocks: [record.data],
17
+ offset: record.offset
18
+ });
19
+ }
20
+ return groups;
21
+ }
22
+ /** The BOF record's own fixed prefix: a two-byte vers followed by a two-byte dt. */
23
+ const BOF_PREFIX_SIZE = 4;
24
+ /** Splits a grouped record sequence into its substreams, verifying each BOF declares BIFF8. */
25
+ function splitSubstreams(groups) {
26
+ const substreams = [];
27
+ let current;
28
+ for (const group of groups) {
29
+ if (group.type === 2057) {
30
+ if (current !== void 0) substreams.push({
31
+ ...current,
32
+ index: substreams.length
33
+ });
34
+ current = {
35
+ documentType: readBofDocumentType(group),
36
+ records: [],
37
+ offset: group.offset
38
+ };
39
+ continue;
40
+ }
41
+ if (group.type === 10) {
42
+ if (current !== void 0) {
43
+ substreams.push({
44
+ ...current,
45
+ index: substreams.length
46
+ });
47
+ current = void 0;
48
+ }
49
+ continue;
50
+ }
51
+ current?.records.push(group);
52
+ }
53
+ if (current !== void 0) substreams.push({
54
+ ...current,
55
+ index: substreams.length
56
+ });
57
+ return substreams;
58
+ }
59
+ /** Reads a BOF's vers and dt ([MS-XLS] 2.4.21), rejecting anything that is not BIFF8. */
60
+ function readBofDocumentType(group) {
61
+ const data = group.blocks[0];
62
+ if (data === void 0 || data.length < BOF_PREFIX_SIZE) throw new BiffFormatError(`BOF record carries ${data?.length ?? 0} bytes, too few for its own version and document type`);
63
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
64
+ const version = view.getUint16(0, true);
65
+ if (version !== 1536) throw new BiffFormatError(`BOF declares BIFF version 0x${version.toString(16).padStart(4, "0")}; this reader implements BIFF8 (0x0600) only`);
66
+ return view.getUint16(2, true);
67
+ }
68
+ //#endregion
69
+ export { groupRecords, splitSubstreams };
@@ -0,0 +1,46 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_biff_records = require("./biff/records.cjs");
3
+ let archive_codec = require("archive-codec");
4
+ //#region src/container.ts
5
+ /** The stream a BIFF8 workbook lives in. Matched exactly and case-sensitively, which is how archive-codec reports a directory entry's name. */
6
+ const WORKBOOK_STREAM = "Workbook";
7
+ /** 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. */
8
+ const LEGACY_WORKBOOK_STREAM = "Book";
9
+ /**
10
+ * Extracts the BIFF8 record stream from a .xls file's compound-file container.
11
+ *
12
+ * Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
13
+ */
14
+ function readWorkbookStream(bytes) {
15
+ if (!(0, archive_codec.isCompoundFile)(bytes)) throw new require_biff_records.BiffFormatError("not a compound file: a .xls workbook is a [MS-CFB] container holding a 'Workbook' stream");
16
+ const streams = readWorkbookContainer(bytes);
17
+ const workbook = streams.find((stream) => stream.path === WORKBOOK_STREAM);
18
+ if (workbook !== void 0) return workbook.bytes;
19
+ if (streams.some((stream) => stream.path === LEGACY_WORKBOOK_STREAM)) throw new require_biff_records.BiffFormatError("compound file holds a 'Book' stream rather than a 'Workbook' stream, so it is a BIFF5/BIFF7 workbook; this reader implements BIFF8 only");
20
+ throw new require_biff_records.BiffFormatError("compound file holds no 'Workbook' stream, so it is not a .xls workbook");
21
+ }
22
+ /** archive-codec's own reader, with its typed error left to propagate and every other failure wrapped, so a caller catching BiffFormatError sees one error type for "this is not a workbook this package can read". */
23
+ function readWorkbookContainer(bytes) {
24
+ try {
25
+ return (0, archive_codec.readCompoundFile)(bytes);
26
+ } catch (error) {
27
+ if (error instanceof archive_codec.CompoundFileFormatError) throw new require_biff_records.BiffFormatError(`compound-file container could not be read: ${error.message}`);
28
+ throw new require_biff_records.BiffFormatError(`compound-file container could not be read: ${error instanceof Error ? error.message : String(error)}`);
29
+ }
30
+ }
31
+ /**
32
+ * Whether these bytes are a workbook this package can read.
33
+ *
34
+ * Checks for the compound-file container AND a "Workbook" stream inside it, rather than the container magic alone: .doc, .ppt, .msg, and a dozen other things are compound files too, so the magic bytes on their own would claim a Word document is a spreadsheet. A Microsoft Works .xlr passes, deliberately -- it carries the same BIFF8 "Workbook" stream.
35
+ */
36
+ function isXlsFile(bytes) {
37
+ if (!(0, archive_codec.isCompoundFile)(bytes)) return false;
38
+ try {
39
+ return (0, archive_codec.readCompoundFile)(bytes).some((stream) => stream.path === WORKBOOK_STREAM);
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+ //#endregion
45
+ exports.isXlsFile = isXlsFile;
46
+ exports.readWorkbookStream = readWorkbookStream;
@@ -0,0 +1,15 @@
1
+ //#region src/container.d.ts
2
+ /**
3
+ * Extracts the BIFF8 record stream from a .xls file's compound-file container.
4
+ *
5
+ * Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
6
+ */
7
+ declare function readWorkbookStream(bytes: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
8
+ /**
9
+ * Whether these bytes are a workbook this package can read.
10
+ *
11
+ * Checks for the compound-file container AND a "Workbook" stream inside it, rather than the container magic alone: .doc, .ppt, .msg, and a dozen other things are compound files too, so the magic bytes on their own would claim a Word document is a spreadsheet. A Microsoft Works .xlr passes, deliberately -- it carries the same BIFF8 "Workbook" stream.
12
+ */
13
+ declare function isXlsFile(bytes: Uint8Array<ArrayBuffer>): boolean;
14
+ //#endregion
15
+ export { isXlsFile, readWorkbookStream };
@@ -0,0 +1,15 @@
1
+ //#region src/container.d.ts
2
+ /**
3
+ * Extracts the BIFF8 record stream from a .xls file's compound-file container.
4
+ *
5
+ * Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
6
+ */
7
+ declare function readWorkbookStream(bytes: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
8
+ /**
9
+ * Whether these bytes are a workbook this package can read.
10
+ *
11
+ * Checks for the compound-file container AND a "Workbook" stream inside it, rather than the container magic alone: .doc, .ppt, .msg, and a dozen other things are compound files too, so the magic bytes on their own would claim a Word document is a spreadsheet. A Microsoft Works .xlr passes, deliberately -- it carries the same BIFF8 "Workbook" stream.
12
+ */
13
+ declare function isXlsFile(bytes: Uint8Array<ArrayBuffer>): boolean;
14
+ //#endregion
15
+ export { isXlsFile, readWorkbookStream };
@@ -0,0 +1,44 @@
1
+ import { BiffFormatError } from "./biff/records.js";
2
+ import { CompoundFileFormatError, isCompoundFile, readCompoundFile } from "archive-codec";
3
+ //#region src/container.ts
4
+ /** The stream a BIFF8 workbook lives in. Matched exactly and case-sensitively, which is how archive-codec reports a directory entry's name. */
5
+ const WORKBOOK_STREAM = "Workbook";
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
+ const LEGACY_WORKBOOK_STREAM = "Book";
8
+ /**
9
+ * Extracts the BIFF8 record stream from a .xls file's compound-file container.
10
+ *
11
+ * Throws rather than returning undefined for anything that is not a readable BIFF8 workbook: a caller wanting a soft answer asks isXlsFile first.
12
+ */
13
+ function readWorkbookStream(bytes) {
14
+ if (!isCompoundFile(bytes)) throw new BiffFormatError("not a compound file: a .xls workbook is a [MS-CFB] container holding a 'Workbook' stream");
15
+ const streams = readWorkbookContainer(bytes);
16
+ const workbook = streams.find((stream) => stream.path === WORKBOOK_STREAM);
17
+ if (workbook !== void 0) return workbook.bytes;
18
+ 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
+ throw new BiffFormatError("compound file holds no 'Workbook' stream, so it is not a .xls workbook");
20
+ }
21
+ /** archive-codec's own reader, with its typed error left to propagate and every other failure wrapped, so a caller catching BiffFormatError sees one error type for "this is not a workbook this package can read". */
22
+ function readWorkbookContainer(bytes) {
23
+ try {
24
+ return readCompoundFile(bytes);
25
+ } catch (error) {
26
+ if (error instanceof CompoundFileFormatError) throw new BiffFormatError(`compound-file container could not be read: ${error.message}`);
27
+ throw new BiffFormatError(`compound-file container could not be read: ${error instanceof Error ? error.message : String(error)}`);
28
+ }
29
+ }
30
+ /**
31
+ * Whether these bytes are a workbook this package can read.
32
+ *
33
+ * Checks for the compound-file container AND a "Workbook" stream inside it, rather than the container magic alone: .doc, .ppt, .msg, and a dozen other things are compound files too, so the magic bytes on their own would claim a Word document is a spreadsheet. A Microsoft Works .xlr passes, deliberately -- it carries the same BIFF8 "Workbook" stream.
34
+ */
35
+ function isXlsFile(bytes) {
36
+ if (!isCompoundFile(bytes)) return false;
37
+ try {
38
+ return readCompoundFile(bytes).some((stream) => stream.path === WORKBOOK_STREAM);
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+ //#endregion
44
+ export { isXlsFile, readWorkbookStream };
@@ -0,0 +1,230 @@
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_container = require("./container.cjs");
5
+ const require_biff_substreams = require("./biff/substreams.cjs");
6
+ const require_number_format = require("./number-format.cjs");
7
+ const require_serial = require("./serial.cjs");
8
+ const require_workbook_globals = require("./workbook/globals.cjs");
9
+ const require_workbook_sheet = require("./workbook/sheet.cjs");
10
+ let document_schema_js = require("document-schema.js");
11
+ //#region src/content.ts
12
+ /** 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
+ const SHEET_TYPE_WORKSHEET = 0;
14
+ /**
15
+ * Print settings this package emits rather than reads.
16
+ *
17
+ * ContentSheetPrintSettings makes pageSize, margins, gridlines, headers, and pageOrder REQUIRED, so a sheet cannot be produced without them, and BIFF8 spreads the real values across the Setup, LeftMargin/RightMargin/TopMargin/BottomMargin, PrintGrid, and PrintRowCol records plus a paper-size code table. None of those is read yet, so these are Excel's own documented "Normal" preset -- the same constants ooxml.js falls back to for an xlsx carrying no pageMargins element -- and they are honest defaults rather than the file's own settings. Reading the real ones is tracked as remaining scope rather than guessed at from unverified field offsets.
18
+ */
19
+ const POINTS_PER_INCH = 72;
20
+ const DEFAULT_PRINT_SETTINGS = {
21
+ pageSize: document_schema_js.PAGE_SIZE_LETTER,
22
+ margins: {
23
+ topPt: .75 * POINTS_PER_INCH,
24
+ rightPt: .7 * POINTS_PER_INCH,
25
+ bottomPt: .75 * POINTS_PER_INCH,
26
+ leftPt: .7 * POINTS_PER_INCH
27
+ },
28
+ gridlines: false,
29
+ headers: false,
30
+ pageOrder: "downThenOver"
31
+ };
32
+ /**
33
+ * Reads a .xls file's bytes into a ContentDocument.
34
+ *
35
+ * The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format.
36
+ */
37
+ function readXlsContent(bytes) {
38
+ const substreams = require_biff_substreams.splitSubstreams(require_biff_substreams.groupRecords(require_biff_records.readRecords(require_container.readWorkbookStream(bytes))));
39
+ const globalsSubstream = substreams[0];
40
+ if (globalsSubstream === void 0) throw new require_biff_records.BiffFormatError("workbook stream holds no substreams, so it carries no globals substream");
41
+ 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
+ const globals = require_workbook_globals.readWorkbookGlobals(globalsSubstream.records);
43
+ return {
44
+ kind: "spreadsheet",
45
+ metadata: {},
46
+ sheets: globals.sheets.filter((entry) => entry.sheetType === SHEET_TYPE_WORKSHEET).map((entry) => readSheet(entry, substreams, globals))
47
+ };
48
+ }
49
+ /** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
50
+ function readXls(bytes) {
51
+ return (0, document_schema_js.assembleTree)(readXlsContent(bytes));
52
+ }
53
+ /**
54
+ * Locates a sheet's own substream and maps it.
55
+ *
56
+ * 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
+ */
58
+ function readSheet(entry, substreams, globals) {
59
+ const substream = substreams.find((candidate) => candidate.offset === entry.bofPosition && candidate.documentType === 16);
60
+ const raw = substream === void 0 ? {
61
+ cells: [],
62
+ rows: [],
63
+ columns: [],
64
+ merges: []
65
+ } : require_workbook_sheet.readSheetRecords(substream.records, globals.sharedStrings);
66
+ return {
67
+ name: entry.name,
68
+ cells: mapCells(raw, globals),
69
+ columns: mapColumns(raw),
70
+ rows: mapRows(raw),
71
+ images: [],
72
+ printSettings: DEFAULT_PRINT_SETTINGS
73
+ };
74
+ }
75
+ function mapRows(raw) {
76
+ const rows = [];
77
+ for (const row of raw.rows) {
78
+ if (row.heightPt === void 0 && !row.hidden) continue;
79
+ const entry = { index: row.index };
80
+ if (row.heightPt !== void 0) entry.heightPt = row.heightPt;
81
+ if (row.hidden) entry.hidden = true;
82
+ rows.push(entry);
83
+ }
84
+ return rows;
85
+ }
86
+ function mapColumns(raw) {
87
+ const columns = [];
88
+ for (const column of raw.columns) {
89
+ if (column.widthPt === void 0 && !column.hidden) continue;
90
+ const entry = { index: column.index };
91
+ if (column.widthPt !== void 0) entry.widthPt = column.widthPt;
92
+ if (column.hidden) entry.hidden = true;
93
+ columns.push(entry);
94
+ }
95
+ return columns;
96
+ }
97
+ /** Maps the raw cells, then stamps merged-range spans onto their anchor cells. */
98
+ function mapCells(raw, globals) {
99
+ const cells = [];
100
+ for (const cell of raw.cells) {
101
+ const mapped = mapCell(cell, globals);
102
+ if (mapped !== void 0) cells.push(mapped);
103
+ }
104
+ applyMerges(cells, raw);
105
+ return cells;
106
+ }
107
+ /**
108
+ * Maps one raw cell, or drops it.
109
+ *
110
+ * A blank cell carrying no merge is dropped: ContentSheet's cell array is documented as sparse, holding only cells with something to show, and a Blank or MulBlank record states formatting this reader does not map yet. Dropping it keeps the array honest rather than filling a sheet with thousands of empty entries -- applyMerges below re-materialises the few blanks that anchor a merged range.
111
+ */
112
+ function mapCell(cell, globals) {
113
+ if (cell.value.kind === "blank") return;
114
+ const formatCode = require_workbook_globals.formatCodeOf(globals, cell.xfIndex);
115
+ const value = resolveValue(cell, formatCode, globals.date1904);
116
+ const mapped = {
117
+ row: cell.row,
118
+ column: cell.column,
119
+ value,
120
+ displayText: displayTextOf(value)
121
+ };
122
+ if (formatCode !== void 0) mapped.numberFormatCode = formatCode;
123
+ return mapped;
124
+ }
125
+ /**
126
+ * Resolves a raw value into a ContentCellValue, classifying a number through its own format code.
127
+ *
128
+ * 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.
129
+ */
130
+ function resolveValue(cell, formatCode, date1904) {
131
+ if (cell.value.kind === "blank") return { kind: "empty" };
132
+ if (cell.value.kind !== "number") return cell.value;
133
+ const num = cell.value.value;
134
+ if (formatCode === void 0) return {
135
+ kind: "number",
136
+ value: num
137
+ };
138
+ const format = require_number_format.classifyNumberFormat(formatCode);
139
+ switch (format.kind) {
140
+ case "percentage": return {
141
+ kind: "percentage",
142
+ value: num
143
+ };
144
+ case "currency": return format.code === void 0 ? {
145
+ kind: "currency",
146
+ value: num
147
+ } : {
148
+ kind: "currency",
149
+ value: num,
150
+ currency: format.code
151
+ };
152
+ case "date": {
153
+ const iso = require_serial.serialToIsoDate(num, date1904);
154
+ return iso === void 0 ? {
155
+ kind: "number",
156
+ value: num
157
+ } : {
158
+ kind: "date",
159
+ value: iso
160
+ };
161
+ }
162
+ case "time": {
163
+ const iso = require_serial.serialToIsoTime(num);
164
+ return iso === void 0 ? {
165
+ kind: "number",
166
+ value: num
167
+ } : {
168
+ kind: "time",
169
+ value: iso
170
+ };
171
+ }
172
+ case "dateTime": {
173
+ const iso = require_serial.serialToIsoDateTime(num, date1904);
174
+ return iso === void 0 ? {
175
+ kind: "number",
176
+ value: num
177
+ } : {
178
+ kind: "dateTime",
179
+ value: iso
180
+ };
181
+ }
182
+ default: return {
183
+ kind: "number",
184
+ value: num
185
+ };
186
+ }
187
+ }
188
+ /** The typed value's own spelling, matching ooxml.js's derivation exactly so the same cell reads identically from either format. Deliberately not the producer's rendered string: this package classifies number formats but does not render through them. */
189
+ function displayTextOf(value) {
190
+ switch (value.kind) {
191
+ case "number":
192
+ case "percentage":
193
+ case "currency": return String(value.value);
194
+ case "boolean": return value.value ? "TRUE" : "FALSE";
195
+ case "date":
196
+ case "time":
197
+ case "dateTime":
198
+ case "string":
199
+ case "error": return value.value;
200
+ case "empty": return "";
201
+ default: return "";
202
+ }
203
+ }
204
+ /**
205
+ * Stamps each merged range's span onto its anchor cell, materialising an empty anchor when the range's top-left cell had no value of its own.
206
+ *
207
+ * ContentSheetCell documents colSpan/rowSpan as belonging to the anchor cell alone, and only when greater than one. A merged range whose anchor is blank is common -- merging cells in Excel keeps only the top-left value, and a range merged over an empty cell has no value anywhere -- so the anchor is created here rather than left absent, which would lose the merge entirely.
208
+ */
209
+ function applyMerges(cells, raw) {
210
+ for (const range of raw.merges) {
211
+ const rowSpan = range.endRow - range.startRow + 1;
212
+ const colSpan = range.endColumn - range.startColumn + 1;
213
+ if (rowSpan <= 1 && colSpan <= 1) continue;
214
+ let anchor = cells.find((cell) => cell.row === range.startRow && cell.column === range.startColumn);
215
+ if (anchor === void 0) {
216
+ anchor = {
217
+ row: range.startRow,
218
+ column: range.startColumn,
219
+ value: { kind: "empty" },
220
+ displayText: ""
221
+ };
222
+ cells.push(anchor);
223
+ }
224
+ if (colSpan > 1) anchor.colSpan = colSpan;
225
+ if (rowSpan > 1) anchor.rowSpan = rowSpan;
226
+ }
227
+ }
228
+ //#endregion
229
+ exports.readXls = readXls;
230
+ exports.readXlsContent = readXlsContent;
@@ -0,0 +1,20 @@
1
+ import { ContentDocument, DocumentTree } from "document-schema.js";
2
+ //#region src/content.d.ts
3
+ /**
4
+ * The spreadsheet member of ContentDocument's own discriminated union.
5
+ *
6
+ * Named and returned in place of the bare union, which is what ooxml.js's readXlsxContent declares. A .xls is a spreadsheet by construction -- there is no input this reader could accept that produced a wordprocessing or presentation document -- so returning the union would force every caller to re-narrow on `kind` to reach `sheets`, discarding a fact this function already knows. The narrowed type stays assignable to ContentDocument, so a caller holding one (documents.js's conversion registry among them) is unaffected.
7
+ */
8
+ type XlsContentDocument = Extract<ContentDocument, {
9
+ kind: "spreadsheet";
10
+ }>;
11
+ /**
12
+ * Reads a .xls file's bytes into a ContentDocument.
13
+ *
14
+ * The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format.
15
+ */
16
+ declare function readXlsContent(bytes: Uint8Array<ArrayBuffer>): XlsContentDocument;
17
+ /** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
18
+ declare function readXls(bytes: Uint8Array<ArrayBuffer>): DocumentTree;
19
+ //#endregion
20
+ export { XlsContentDocument, readXls, readXlsContent };
@@ -0,0 +1,20 @@
1
+ import { ContentDocument, DocumentTree } from "document-schema.js";
2
+ //#region src/content.d.ts
3
+ /**
4
+ * The spreadsheet member of ContentDocument's own discriminated union.
5
+ *
6
+ * Named and returned in place of the bare union, which is what ooxml.js's readXlsxContent declares. A .xls is a spreadsheet by construction -- there is no input this reader could accept that produced a wordprocessing or presentation document -- so returning the union would force every caller to re-narrow on `kind` to reach `sheets`, discarding a fact this function already knows. The narrowed type stays assignable to ContentDocument, so a caller holding one (documents.js's conversion registry among them) is unaffected.
7
+ */
8
+ type XlsContentDocument = Extract<ContentDocument, {
9
+ kind: "spreadsheet";
10
+ }>;
11
+ /**
12
+ * Reads a .xls file's bytes into a ContentDocument.
13
+ *
14
+ * The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format.
15
+ */
16
+ declare function readXlsContent(bytes: Uint8Array<ArrayBuffer>): XlsContentDocument;
17
+ /** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
18
+ declare function readXls(bytes: Uint8Array<ArrayBuffer>): DocumentTree;
19
+ //#endregion
20
+ export { XlsContentDocument, readXls, readXlsContent };