@office-kit/xlsx 0.16.0 → 0.18.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 +89 -4
- package/dist/cell/cell.d.ts +12 -19
- package/dist/{cell-Df3cKRIl.mjs → cell-B5OUUdfI.mjs} +13 -7
- package/dist/cell-B5OUUdfI.mjs.map +1 -0
- package/dist/{cell-style-CNsET6WU.mjs → cell-style-CbB4nXTY.mjs} +11 -4
- package/dist/cell-style-CbB4nXTY.mjs.map +1 -0
- package/dist/cell.mjs +1 -1
- package/dist/{coordinate-Bt5Gni2I.mjs → coordinate-CxC88uBk.mjs} +31 -2
- package/dist/coordinate-CxC88uBk.mjs.map +1 -0
- package/dist/{defined-names-Ctu3F6ls.mjs → defined-names-Cmp8rbvI.mjs} +2 -2
- package/dist/{defined-names-Ctu3F6ls.mjs.map → defined-names-Cmp8rbvI.mjs.map} +1 -1
- package/dist/{differential-BPKa7wXi.mjs → differential-QLaKEiBk.mjs} +2 -2
- package/dist/{differential-BPKa7wXi.mjs.map → differential-QLaKEiBk.mjs.map} +1 -1
- package/dist/{drawing-ZJ3h4VHD.mjs → drawing-Cv6UtInY.mjs} +2 -2
- package/dist/{drawing-ZJ3h4VHD.mjs.map → drawing-Cv6UtInY.mjs.map} +1 -1
- package/dist/drawing.mjs +1 -1
- package/dist/{escape-rnWSPpWY.mjs → escape-rA5UpibT.mjs} +26 -3
- package/dist/escape-rA5UpibT.mjs.map +1 -0
- package/dist/io/load.d.ts +30 -0
- package/dist/io.mjs +2 -2
- package/dist/{iterparse-DZE0zy2M.mjs → iterparse-CZoRXvZW.mjs} +2 -2
- package/dist/{iterparse-DZE0zy2M.mjs.map → iterparse-CZoRXvZW.mjs.map} +1 -1
- package/dist/{load-B1sK-LjT.mjs → load-BpnCPv1S.mjs} +84 -45
- package/dist/load-BpnCPv1S.mjs.map +1 -0
- package/dist/{manifest-pLmx7KYh.mjs → manifest-9p0ccqmR.mjs} +10 -11
- package/dist/manifest-9p0ccqmR.mjs.map +1 -0
- package/dist/node.mjs +1 -1
- package/dist/packaging.mjs +1 -1
- package/dist/{parser-By6RWZVW.mjs → parser-B1tB9YaE.mjs} +15 -5
- package/dist/parser-B1tB9YaE.mjs.map +1 -0
- package/dist/{reader-DHMxLBQV.mjs → reader-pC7bxYAs.mjs} +30 -7
- package/dist/reader-pC7bxYAs.mjs.map +1 -0
- package/dist/{save-DmdWwxmw.mjs → save-1Z3344nz.mjs} +8 -8
- package/dist/{save-DmdWwxmw.mjs.map → save-1Z3344nz.mjs.map} +1 -1
- package/dist/schema.mjs +1 -1
- package/dist/{serialize-BC2Wu3bR.mjs → serialize-BtxgssFK.mjs} +24 -7
- package/dist/serialize-BtxgssFK.mjs.map +1 -0
- package/dist/{serializer-C6nm0mRJ.mjs → serializer-N6XIwsgp.mjs} +3 -3
- package/dist/{serializer-C6nm0mRJ.mjs.map → serializer-N6XIwsgp.mjs.map} +1 -1
- package/dist/streaming.mjs +129 -77
- package/dist/streaming.mjs.map +1 -1
- package/dist/styles/display-text.d.ts +95 -0
- package/dist/styles/format-code.d.ts +125 -0
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/numbers.d.ts +7 -0
- package/dist/styles.mjs +1161 -4
- package/dist/styles.mjs.map +1 -0
- package/dist/{stylesheet-writer-oXSuGnJy.mjs → stylesheet-writer-BEn-0P0x.mjs} +480 -173
- package/dist/stylesheet-writer-BEn-0P0x.mjs.map +1 -0
- package/dist/{table-BrP0RwUd.mjs → table-BxOLUZs3.mjs} +2 -2
- package/dist/{table-BrP0RwUd.mjs.map → table-BxOLUZs3.mjs.map} +1 -1
- package/dist/{tree-BX-sRRVp.mjs → tree-CspNjuYl.mjs} +12 -2
- package/dist/tree-CspNjuYl.mjs.map +1 -0
- package/dist/utils/cell-number.d.ts +10 -0
- package/dist/utils/coordinate.d.ts +11 -0
- package/dist/utils/escape.d.ts +17 -0
- package/dist/utils/xsd-boolean.d.ts +9 -0
- package/dist/utils.mjs +2 -2
- package/dist/workbook/index.d.ts +1 -1
- package/dist/workbook/workbook.d.ts +9 -46
- package/dist/{workbook-DOTFzCpz.mjs → workbook-tG9oYtzU.mjs} +50 -13
- package/dist/workbook-tG9oYtzU.mjs.map +1 -0
- package/dist/workbook.mjs +3 -3
- package/dist/worksheet/index.d.ts +1 -1
- package/dist/worksheet/worksheet.d.ts +55 -14
- package/dist/{worksheet-B2NOQ6dM.mjs → worksheet-DLMyDD7W.mjs} +87 -22
- package/dist/worksheet-DLMyDD7W.mjs.map +1 -0
- package/dist/worksheet.mjs +3 -3
- package/dist/xml/namespaces.d.ts +7 -0
- package/dist/xml/parser.d.ts +12 -0
- package/dist/xml/strict-package.d.ts +14 -0
- package/dist/xml.mjs +5 -5
- package/dist/zip.mjs +1 -1
- package/package.json +1 -1
- package/dist/cell-Df3cKRIl.mjs.map +0 -1
- package/dist/cell-style-CNsET6WU.mjs.map +0 -1
- package/dist/coordinate-Bt5Gni2I.mjs.map +0 -1
- package/dist/escape-rnWSPpWY.mjs.map +0 -1
- package/dist/load-B1sK-LjT.mjs.map +0 -1
- package/dist/manifest-pLmx7KYh.mjs.map +0 -1
- package/dist/parser-By6RWZVW.mjs.map +0 -1
- package/dist/reader-DHMxLBQV.mjs.map +0 -1
- package/dist/serialize-BC2Wu3bR.mjs.map +0 -1
- package/dist/stylesheet-writer-oXSuGnJy.mjs.map +0 -1
- package/dist/tree-BX-sRRVp.mjs.map +0 -1
- package/dist/workbook-DOTFzCpz.mjs.map +0 -1
- package/dist/worksheet-B2NOQ6dM.mjs.map +0 -1
package/dist/streaming.mjs
CHANGED
|
@@ -1,61 +1,35 @@
|
|
|
1
1
|
import { i as OpenXmlIoError, o as OpenXmlSchemaError } from "./exceptions-D-CFwxgm.mjs";
|
|
2
|
-
import { s as coordinateToTuple } from "./coordinate-
|
|
2
|
+
import { _ as rowNumberFromAttr, c as derivedRowNumber, s as coordinateToTuple } from "./coordinate-CxC88uBk.mjs";
|
|
3
3
|
import { t as ERROR_CODES } from "./inference-BMvv9cUY.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { _ as ARC_WORKBOOK_RELS,
|
|
6
|
-
import { t as stylesheetToBytes, v as serializeCell } from "./stylesheet-writer-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { A as
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
4
|
+
import { a as unescapeCellString, n as escapeXmlAttr } from "./escape-rA5UpibT.mjs";
|
|
5
|
+
import { Tt as XLSX_TYPE, _ as ARC_WORKBOOK_RELS, bt as WORKSHEET_TYPE, c as ARC_CONTENT_TYPES, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, jt as localNameOf, lt as REL_NS, m as ARC_STYLE, mt as STYLES_TYPE, p as ARC_SHARED_STRINGS, ut as SHARED_STRINGS_TYPE } from "./tree-CspNjuYl.mjs";
|
|
6
|
+
import { t as stylesheetToBytes, v as serializeCell, x as parseCellNumber } from "./stylesheet-writer-BEn-0P0x.mjs";
|
|
7
|
+
import { n as parseXml } from "./parser-B1tB9YaE.mjs";
|
|
8
|
+
import { r as parseXsdBoolean } from "./serialize-BtxgssFK.mjs";
|
|
9
|
+
import { $ as findByType, Q as findById, a as makeManifest, n as addOverride, nt as relsFromBytes, o as manifestFromBytes, rt as relsToBytes, s as manifestToBytes, t as addDefault, tt as makeRelationships } from "./manifest-9p0ccqmR.mjs";
|
|
10
|
+
import { A as parseSharedStringsXml, C as validateSheetTitle, F as assertNotStrictRoot, O as makeSharedStrings, P as assertNotStrictRelTypes, j as serializeRichString } from "./workbook-tG9oYtzU.mjs";
|
|
11
|
+
import { ct as buildXfPatch, ht as makeStylesheet, it as addCellXf, lt as defaultCellXf } from "./cell-style-CbB4nXTY.mjs";
|
|
12
|
+
import { c as readOptionalWorkbookPart, i as WORKBOOK_TAG, l as resolveRelTarget, n as SHARED_STRINGS_PART, o as parseDate1904, r as STYLES_PART, s as parseSheetEntries, t as OFFICE_DOC_REL_TYPE, u as parseStylesheetXml } from "./load-BpnCPv1S.mjs";
|
|
13
|
+
import { t as openZip } from "./reader-pC7bxYAs.mjs";
|
|
13
14
|
import { t as utf8ByteLength } from "./utf8-OAkCDG5g.mjs";
|
|
14
15
|
import { t as createZipWriter } from "./writer-C38RBZCy.mjs";
|
|
15
|
-
import { t as iterParse } from "./iterparse-
|
|
16
|
+
import { t as iterParse } from "./iterparse-CZoRXvZW.mjs";
|
|
16
17
|
//#region src/streaming/read-only.ts
|
|
17
|
-
const SHEET_TAG = `{${SHEET_MAIN_NS}}sheet`;
|
|
18
|
-
const SHEETS_TAG = `{${SHEET_MAIN_NS}}sheets`;
|
|
19
|
-
const parseSheetList = (root, workbookPath, archive) => {
|
|
20
|
-
const sheetsEl = findChild(root, SHEETS_TAG);
|
|
21
|
-
if (!sheetsEl) return [];
|
|
22
|
-
const wbRelsPath = relsPathFor(workbookPath);
|
|
23
|
-
const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : { rels: [] };
|
|
24
|
-
const out = [];
|
|
25
|
-
for (const sheet of findChildren(sheetsEl, SHEET_TAG)) {
|
|
26
|
-
const name = sheet.attrs["name"];
|
|
27
|
-
const rId = sheet.attrs[`{${REL_NS}}id`];
|
|
28
|
-
if (!name || !rId) continue;
|
|
29
|
-
const rel = findById(wbRels, rId);
|
|
30
|
-
if (!rel) continue;
|
|
31
|
-
const partPath = resolveRelTarget(workbookPath, rel.target);
|
|
32
|
-
out.push({
|
|
33
|
-
name,
|
|
34
|
-
rId,
|
|
35
|
-
partPath
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
};
|
|
40
18
|
const relsPathFor = (partPath) => {
|
|
41
19
|
const i = partPath.lastIndexOf("/");
|
|
42
20
|
if (i < 0) return `_rels/${partPath}.rels`;
|
|
43
21
|
return `${partPath.slice(0, i)}/_rels/${partPath.slice(i + 1)}.rels`;
|
|
44
22
|
};
|
|
45
|
-
const
|
|
46
|
-
const i = qname.lastIndexOf("}");
|
|
47
|
-
return i < 0 ? qname : qname.slice(i + 1);
|
|
48
|
-
};
|
|
49
|
-
const decodeCellValue = (t, vText, inlineText, sst) => {
|
|
23
|
+
const decodeCellValue = (t, vText, inlineText, sst, sheet, col, row) => {
|
|
50
24
|
switch (t) {
|
|
51
|
-
case "n": return vText
|
|
25
|
+
case "n": return parseCellNumber(vText, sheet, col, row);
|
|
52
26
|
case "s": {
|
|
53
27
|
if (vText === void 0) return null;
|
|
54
28
|
const idx = Number.parseInt(vText, 10);
|
|
55
29
|
if (!Number.isInteger(idx) || idx < 0 || idx >= sst.length) return null;
|
|
56
30
|
return sst[idx] ?? null;
|
|
57
31
|
}
|
|
58
|
-
case "b": return vText
|
|
32
|
+
case "b": return parseXsdBoolean(vText) ?? null;
|
|
59
33
|
case "e":
|
|
60
34
|
if (!vText || !ERROR_CODES.has(vText)) return null;
|
|
61
35
|
return {
|
|
@@ -71,15 +45,37 @@ const decodeCellValue = (t, vText, inlineText, sst) => {
|
|
|
71
45
|
* SAX-iterate `<sheetData>/<row>/<c>` events out of the worksheet bytes (or a
|
|
72
46
|
* stream that yields them), yielding one `ReadOnlyCell[]` per row that matches
|
|
73
47
|
* `opts`.
|
|
48
|
+
*
|
|
49
|
+
* `readSheetData` in `../worksheet/reader.ts` walks the same element shapes for
|
|
50
|
+
* `loadWorkbook`. The two stay separate because this one is an async generator
|
|
51
|
+
* over a stream with a row band and an early exit, and that per-event cost is
|
|
52
|
+
* what the other one exists to avoid. Where a row or cell with no `@r` lands
|
|
53
|
+
* has to come out the same in both, and is asserted to: see "agrees with
|
|
54
|
+
* loadWorkbook on where every cell lands" in
|
|
55
|
+
* `tests/worksheet/row-without-r-attribute.test.ts`.
|
|
74
56
|
*/
|
|
75
|
-
async function* iterSheetRows(sheetInput, sst, opts) {
|
|
57
|
+
async function* iterSheetRows(title, sheetInput, sst, opts) {
|
|
76
58
|
const minRow = opts.minRow ?? 1;
|
|
77
59
|
const maxRow = opts.maxRow ?? Number.POSITIVE_INFINITY;
|
|
78
60
|
const minCol = opts.minCol ?? 1;
|
|
79
61
|
const maxCol = opts.maxCol ?? Number.POSITIVE_INFINITY;
|
|
80
62
|
let inSheetData = false;
|
|
81
63
|
let currentRow = -1;
|
|
64
|
+
let nextRow = 1;
|
|
82
65
|
let currentCells = [];
|
|
66
|
+
let nextCol = 1;
|
|
67
|
+
let pendingCells = [];
|
|
68
|
+
const settleRow = (row) => {
|
|
69
|
+
currentRow = row;
|
|
70
|
+
nextRow = Math.max(nextRow, row + 1);
|
|
71
|
+
if (row >= minRow && row <= maxRow) for (const cell of pendingCells) currentCells.push({
|
|
72
|
+
row,
|
|
73
|
+
col: cell.col,
|
|
74
|
+
value: decodeCellValue(cell.type, cell.text, cell.inline, sst, title, cell.col, row),
|
|
75
|
+
styleId: cell.styleId
|
|
76
|
+
});
|
|
77
|
+
pendingCells = [];
|
|
78
|
+
};
|
|
83
79
|
let cellOpen = false;
|
|
84
80
|
let cellRow = 0;
|
|
85
81
|
let cellCol = 0;
|
|
@@ -91,38 +87,50 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
91
87
|
let inIsT = false;
|
|
92
88
|
let isText = "";
|
|
93
89
|
let isRunText = "";
|
|
90
|
+
let checkedRoot = false;
|
|
94
91
|
for await (const ev of iterParse(sheetInput)) {
|
|
95
92
|
const e = ev;
|
|
96
93
|
if (e.kind === "start") {
|
|
97
|
-
|
|
94
|
+
if (!checkedRoot) {
|
|
95
|
+
assertNotStrictRoot(e.name);
|
|
96
|
+
checkedRoot = true;
|
|
97
|
+
}
|
|
98
|
+
const local = localNameOf(e.name);
|
|
98
99
|
if (!inSheetData) {
|
|
99
100
|
if (local === "sheetData") inSheetData = true;
|
|
100
101
|
continue;
|
|
101
102
|
}
|
|
102
103
|
switch (local) {
|
|
103
104
|
case "row": {
|
|
104
|
-
e.attrs;
|
|
105
105
|
const rRaw = e.attrs["r"];
|
|
106
|
-
|
|
106
|
+
if (rRaw === void 0) currentRow = 0;
|
|
107
|
+
else {
|
|
108
|
+
currentRow = rowNumberFromAttr(rRaw, "loadWorkbookStream");
|
|
109
|
+
nextRow = Math.max(nextRow, currentRow + 1);
|
|
110
|
+
}
|
|
107
111
|
currentCells = [];
|
|
112
|
+
pendingCells = [];
|
|
113
|
+
nextCol = 1;
|
|
108
114
|
break;
|
|
109
115
|
}
|
|
110
116
|
case "c": {
|
|
111
117
|
if (currentRow < 0) break;
|
|
112
|
-
|
|
118
|
+
const ref = e.attrs["r"];
|
|
119
|
+
if (currentRow === 0 && ref) settleRow(coordinateToTuple(ref).row);
|
|
120
|
+
if (currentRow !== 0 && (currentRow < minRow || currentRow > maxRow)) break;
|
|
113
121
|
cellOpen = true;
|
|
114
122
|
cellType = e.attrs["t"] ?? "n";
|
|
115
123
|
const sRaw = e.attrs["s"];
|
|
116
124
|
cellStyleId = sRaw ? Number.parseInt(sRaw, 10) || 0 : 0;
|
|
117
|
-
const ref = e.attrs["r"];
|
|
118
125
|
if (ref) {
|
|
119
126
|
const tup = coordinateToTuple(ref);
|
|
120
127
|
cellRow = tup.row;
|
|
121
128
|
cellCol = tup.col;
|
|
122
129
|
} else {
|
|
123
130
|
cellRow = currentRow;
|
|
124
|
-
cellCol =
|
|
131
|
+
cellCol = nextCol;
|
|
125
132
|
}
|
|
133
|
+
nextCol = cellCol + 1;
|
|
126
134
|
vText = "";
|
|
127
135
|
isText = "";
|
|
128
136
|
break;
|
|
@@ -148,13 +156,14 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
148
156
|
else if (inIsT) isRunText += e.text;
|
|
149
157
|
continue;
|
|
150
158
|
}
|
|
151
|
-
const local =
|
|
159
|
+
const local = localNameOf(e.name);
|
|
152
160
|
if (!inSheetData) continue;
|
|
153
161
|
switch (local) {
|
|
154
162
|
case "sheetData":
|
|
155
163
|
inSheetData = false;
|
|
156
164
|
return;
|
|
157
165
|
case "row":
|
|
166
|
+
if (currentRow === 0) settleRow(derivedRowNumber(nextRow, "loadWorkbookStream"));
|
|
158
167
|
if (currentRow >= minRow && currentRow <= maxRow && currentCells.length > 0) yield currentCells;
|
|
159
168
|
if (currentRow > maxRow) {
|
|
160
169
|
inSheetData = false;
|
|
@@ -164,8 +173,15 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
164
173
|
currentCells = [];
|
|
165
174
|
break;
|
|
166
175
|
case "c":
|
|
167
|
-
if (cellOpen &&
|
|
168
|
-
|
|
176
|
+
if (cellOpen && cellRow === 0 && cellCol >= minCol && cellCol <= maxCol) pendingCells.push({
|
|
177
|
+
col: cellCol,
|
|
178
|
+
type: cellType,
|
|
179
|
+
text: vText,
|
|
180
|
+
inline: isText,
|
|
181
|
+
styleId: cellStyleId
|
|
182
|
+
});
|
|
183
|
+
else if (cellOpen && cellCol >= minCol && cellCol <= maxCol && cellRow >= minRow && cellRow <= maxRow) {
|
|
184
|
+
const value = decodeCellValue(cellType, vText, isText, sst, title, cellCol, cellRow);
|
|
169
185
|
currentCells.push({
|
|
170
186
|
row: cellRow,
|
|
171
187
|
col: cellCol,
|
|
@@ -194,7 +210,9 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
194
210
|
const isXmlSpace = (b) => b === 32 || b === 9 || b === 10 || b === 13;
|
|
195
211
|
/**
|
|
196
212
|
* Read the `r="N"` row number out of a `<row …>` attribute region, scanning
|
|
197
|
-
* bytes directly. Returns -1 when the attribute is absent or
|
|
213
|
+
* bytes directly. Returns -1 when the attribute is absent, or holds anything
|
|
214
|
+
* `parseRowNumberAttr` rejects: the two accept the same shapes, so the index
|
|
215
|
+
* can never seek by a number the SAX walk reads differently.
|
|
198
216
|
*
|
|
199
217
|
* Decoding the region to a string and running a regex instead costs one
|
|
200
218
|
* TextDecoder, one string and one match array per row, which on a million-row
|
|
@@ -204,9 +222,11 @@ const readRowAttr = (bytes, from, to) => {
|
|
|
204
222
|
for (let p = from; p + 3 < to; p++) {
|
|
205
223
|
if (!isXmlSpace(bytes[p])) continue;
|
|
206
224
|
if (bytes[p + 1] !== 114 || bytes[p + 2] !== 61 || bytes[p + 3] !== 34) continue;
|
|
225
|
+
let q = p + 4;
|
|
226
|
+
while (q < to && isXmlSpace(bytes[q])) q++;
|
|
227
|
+
if (bytes[q] === 43) q++;
|
|
207
228
|
let value = 0;
|
|
208
229
|
let digits = 0;
|
|
209
|
-
let q = p + 4;
|
|
210
230
|
while (q < to) {
|
|
211
231
|
const d = bytes[q];
|
|
212
232
|
if (d === void 0 || d < 48 || d > 57) break;
|
|
@@ -214,7 +234,8 @@ const readRowAttr = (bytes, from, to) => {
|
|
|
214
234
|
digits++;
|
|
215
235
|
q++;
|
|
216
236
|
}
|
|
217
|
-
|
|
237
|
+
while (q < to && isXmlSpace(bytes[q])) q++;
|
|
238
|
+
if (digits > 0 && value >= 1 && value <= 1048576 && q < to && bytes[q] === 34) return value;
|
|
218
239
|
}
|
|
219
240
|
return -1;
|
|
220
241
|
};
|
|
@@ -277,9 +298,14 @@ const findSheetDataTagEnd = (bytes) => {
|
|
|
277
298
|
* `sheetDataEnd` is the byte offset of `</sheetData>` so callers can clip the
|
|
278
299
|
* region that gets handed to saxes. `sheetDataTagEnd` includes the original
|
|
279
300
|
* namespace-bearing ancestor tags, reused by {@link replayFromRow}.
|
|
301
|
+
*
|
|
302
|
+
* `hasUnnumberedRow` reports a `<row>` this scan cannot number, which makes the
|
|
303
|
+
* whole sheet unseekable: a row without `@r` takes the row its first cell
|
|
304
|
+
* names, and reading cell refs is the SAX walk's job.
|
|
280
305
|
*/
|
|
281
306
|
const buildRowOffsetIndex = (bytes) => {
|
|
282
307
|
const out = [];
|
|
308
|
+
let hasUnnumberedRow = false;
|
|
283
309
|
let sheetDataEnd = -1;
|
|
284
310
|
let i = 0;
|
|
285
311
|
while (i < bytes.length) {
|
|
@@ -309,13 +335,15 @@ const buildRowOffsetIndex = (bytes) => {
|
|
|
309
335
|
row,
|
|
310
336
|
offset: start
|
|
311
337
|
});
|
|
338
|
+
else hasUnnumberedRow = true;
|
|
312
339
|
i = j + 1;
|
|
313
340
|
}
|
|
314
341
|
if (sheetDataEnd < 0) sheetDataEnd = bytes.length;
|
|
315
342
|
return {
|
|
316
343
|
index: out,
|
|
317
344
|
sheetDataEnd,
|
|
318
|
-
sheetDataTagEnd: findSheetDataTagEnd(bytes)
|
|
345
|
+
sheetDataTagEnd: findSheetDataTagEnd(bytes),
|
|
346
|
+
hasUnnumberedRow
|
|
319
347
|
};
|
|
320
348
|
};
|
|
321
349
|
/**
|
|
@@ -365,7 +393,9 @@ const replayFromRow = (bytes, sheetDataTagEnd, fromOffset, sheetDataEnd) => {
|
|
|
365
393
|
* `iterRows({ minRow > 1 })` needs random access instead, so the first band
|
|
366
394
|
* query inflates the part and indexes its row offsets, and holds both for the
|
|
367
395
|
* life of the worksheet handle or until the workbook closes. Subsequent band
|
|
368
|
-
* queries jump straight to the first matching row without inflating or scanning
|
|
396
|
+
* queries jump straight to the first matching row without inflating or scanning
|
|
397
|
+
* again. A sheet whose rows omit `@r` cannot be indexed by row number, so its
|
|
398
|
+
* band queries keep streaming and hold nothing.
|
|
369
399
|
*/
|
|
370
400
|
const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes) => {
|
|
371
401
|
const cacheKey = {};
|
|
@@ -373,9 +403,13 @@ const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes)
|
|
|
373
403
|
let cached = indexes.byWorksheet.get(cacheKey);
|
|
374
404
|
if (!cached) {
|
|
375
405
|
const bytes = archive.read(partPath);
|
|
376
|
-
|
|
406
|
+
const { hasUnnumberedRow, index, sheetDataEnd, sheetDataTagEnd } = buildRowOffsetIndex(bytes);
|
|
407
|
+
cached = hasUnnumberedRow ? { seekable: false } : {
|
|
408
|
+
seekable: true,
|
|
377
409
|
bytes,
|
|
378
|
-
|
|
410
|
+
index,
|
|
411
|
+
sheetDataEnd,
|
|
412
|
+
sheetDataTagEnd
|
|
379
413
|
};
|
|
380
414
|
indexes.byWorksheet.set(cacheKey, cached);
|
|
381
415
|
}
|
|
@@ -383,14 +417,16 @@ const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes)
|
|
|
383
417
|
};
|
|
384
418
|
const iterRows = (opts = {}) => {
|
|
385
419
|
const minRow = opts.minRow ?? 1;
|
|
386
|
-
if (minRow <= 1) return iterSheetRows(archive.readStream(partPath), sst, opts);
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
420
|
+
if (minRow <= 1) return iterSheetRows(title, archive.readStream(partPath), sst, opts);
|
|
421
|
+
const seek = ensureIndexed();
|
|
422
|
+
if (!seek.seekable) return iterSheetRows(title, archive.readStream(partPath), sst, opts);
|
|
423
|
+
const { bytes, index, sheetDataEnd, sheetDataTagEnd } = seek;
|
|
424
|
+
if (index.length === 0 || sheetDataTagEnd < 0) return iterSheetRows(title, bytes, sst, opts);
|
|
389
425
|
const pos = firstRowAtOrAfter(index, minRow);
|
|
390
426
|
if (pos < 0) return (async function* () {})();
|
|
391
427
|
const target = index[pos];
|
|
392
|
-
if (!target) return iterSheetRows(bytes, sst, opts);
|
|
393
|
-
return iterSheetRows(replayFromRow(bytes, sheetDataTagEnd, target.offset, sheetDataEnd), sst, opts);
|
|
428
|
+
if (!target) return iterSheetRows(title, bytes, sst, opts);
|
|
429
|
+
return iterSheetRows(title, replayFromRow(bytes, sheetDataTagEnd, target.offset, sheetDataEnd), sst, opts);
|
|
394
430
|
};
|
|
395
431
|
const iterValues = async function* (opts = {}) {
|
|
396
432
|
for await (const row of iterRows(opts)) yield row.map((c) => c.value);
|
|
@@ -406,16 +442,16 @@ const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes)
|
|
|
406
442
|
* sheet list / styles / shared strings. Per the project-wide "no classes" rule
|
|
407
443
|
* (CLAUDE.md), the workbook is a plain object closing over the archive handle.
|
|
408
444
|
*/
|
|
409
|
-
const makeStreamingReadOnlyWorkbook = (sheetNames, styles, date1904, archive,
|
|
445
|
+
const makeStreamingReadOnlyWorkbook = (sheetNames, styles, date1904, archive, partPathByName, sst) => {
|
|
410
446
|
const indexes = { byWorksheet: /* @__PURE__ */ new WeakMap() };
|
|
411
447
|
return {
|
|
412
448
|
sheetNames,
|
|
413
449
|
styles,
|
|
414
450
|
date1904,
|
|
415
451
|
openWorksheet(name) {
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
return makeStreamingReadOnlyWorksheet(name, archive,
|
|
452
|
+
const partPath = partPathByName.get(name);
|
|
453
|
+
if (partPath === void 0) throw new OpenXmlSchemaError(`loadWorkbookStream: no worksheet named "${name}"`);
|
|
454
|
+
return makeStreamingReadOnlyWorksheet(name, archive, partPath, sst, indexes);
|
|
419
455
|
},
|
|
420
456
|
async close() {
|
|
421
457
|
archive.close();
|
|
@@ -429,19 +465,35 @@ async function loadWorkbookStream(source, opts = {}) {
|
|
|
429
465
|
if (!archive.has("[Content_Types].xml")) throw new OpenXmlSchemaError(`loadWorkbookStream: missing "${ARC_CONTENT_TYPES}"`);
|
|
430
466
|
manifestFromBytes(archive.read(ARC_CONTENT_TYPES));
|
|
431
467
|
if (!archive.has(ARC_ROOT_RELS)) throw new OpenXmlSchemaError(`loadWorkbookStream: missing "${ARC_ROOT_RELS}"`);
|
|
432
|
-
const
|
|
433
|
-
|
|
468
|
+
const rootRels = relsFromBytes(archive.read(ARC_ROOT_RELS));
|
|
469
|
+
const officeDocRel = findByType(rootRels, OFFICE_DOC_REL_TYPE);
|
|
470
|
+
if (!officeDocRel) {
|
|
471
|
+
assertNotStrictRelTypes(rootRels.rels.map((r) => r.type));
|
|
472
|
+
throw new OpenXmlSchemaError(`loadWorkbookStream: no officeDocument relationship in root rels`);
|
|
473
|
+
}
|
|
434
474
|
const workbookPath = resolveRelTarget("", officeDocRel.target);
|
|
435
475
|
if (!archive.has(workbookPath)) throw new OpenXmlSchemaError(`loadWorkbookStream: workbook part "${workbookPath}" missing`);
|
|
436
476
|
const workbookRoot = parseXml(archive.read(workbookPath));
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (archive.has(
|
|
444
|
-
|
|
477
|
+
if (workbookRoot.name !== WORKBOOK_TAG) {
|
|
478
|
+
assertNotStrictRoot(workbookRoot.name);
|
|
479
|
+
throw new OpenXmlSchemaError(`loadWorkbookStream: ${workbookPath} root is "${workbookRoot.name}", expected workbook`);
|
|
480
|
+
}
|
|
481
|
+
const declaredSheets = parseSheetEntries(workbookRoot);
|
|
482
|
+
const wbRelsPath = relsPathFor(workbookPath);
|
|
483
|
+
if (declaredSheets.length > 0 && !archive.has(wbRelsPath)) throw new OpenXmlSchemaError(`loadWorkbookStream: workbook has sheets but rels part "${wbRelsPath}" is missing`);
|
|
484
|
+
const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : makeRelationships();
|
|
485
|
+
const partPathByName = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const declared of declaredSheets) {
|
|
487
|
+
if (partPathByName.has(declared.name)) throw new OpenXmlSchemaError(`loadWorkbookStream: duplicate sheet name "${declared.name}"`);
|
|
488
|
+
const rel = findById(wbRels, declared.rId);
|
|
489
|
+
if (!rel) throw new OpenXmlSchemaError(`loadWorkbookStream: sheet "${declared.name}" rId "${declared.rId}" has no matching rels entry`);
|
|
490
|
+
partPathByName.set(declared.name, resolveRelTarget(workbookPath, rel.target));
|
|
491
|
+
}
|
|
492
|
+
const sstBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, SHARED_STRINGS_PART);
|
|
493
|
+
const sst = sstBytes === void 0 ? makeSharedStrings() : parseSharedStringsXml(sstBytes);
|
|
494
|
+
const stylesBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, STYLES_PART);
|
|
495
|
+
const styles = stylesBytes === void 0 ? makeStylesheet() : parseStylesheetXml(stylesBytes);
|
|
496
|
+
return makeStreamingReadOnlyWorkbook(declaredSheets.map((e) => e.name), styles, parseDate1904(workbookRoot), archive, partPathByName, sst.entries.map((e) => typeof e === "string" ? e : e.runs.map((r) => r.text).join("")));
|
|
445
497
|
}
|
|
446
498
|
//#endregion
|
|
447
499
|
//#region src/streaming/string-table.ts
|