@office-kit/xlsx 0.15.0 → 0.17.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 +29 -8
- 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-DjKKPWDL.mjs} +2 -2
- package/dist/{cell-style-CNsET6WU.mjs.map → cell-style-DjKKPWDL.mjs.map} +1 -1
- 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-7KGzbliB.mjs} +2 -2
- package/dist/{differential-BPKa7wXi.mjs.map → differential-7KGzbliB.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 +28 -0
- package/dist/io.mjs +2 -2
- package/dist/{load-CspGc399.mjs → load-DIdQ_zum.mjs} +66 -39
- package/dist/{load-CspGc399.mjs.map → load-DIdQ_zum.mjs.map} +1 -1
- package/dist/{manifest-pLmx7KYh.mjs → manifest-BHUYpjUG.mjs} +3 -3
- package/dist/{manifest-pLmx7KYh.mjs.map → manifest-BHUYpjUG.mjs.map} +1 -1
- package/dist/node.mjs +1 -1
- package/dist/packaging.mjs +1 -1
- package/dist/{reader-DHMxLBQV.mjs → reader-pC7bxYAs.mjs} +30 -7
- package/dist/reader-pC7bxYAs.mjs.map +1 -0
- package/dist/{save-DgPf1DyF.mjs → save-ax11Gsw0.mjs} +7 -7
- package/dist/{save-DgPf1DyF.mjs.map → save-ax11Gsw0.mjs.map} +1 -1
- package/dist/schema.mjs +1 -1
- package/dist/{serialize-BC2Wu3bR.mjs → serialize-Dmb6Vtt9.mjs} +23 -6
- package/dist/serialize-Dmb6Vtt9.mjs.map +1 -0
- package/dist/{serializer-C6nm0mRJ.mjs → serializer-dhv07Z6F.mjs} +2 -2
- package/dist/{serializer-C6nm0mRJ.mjs.map → serializer-dhv07Z6F.mjs.map} +1 -1
- package/dist/streaming/string-table.d.ts +7 -0
- package/dist/streaming.mjs +189 -83
- package/dist/streaming.mjs.map +1 -1
- package/dist/styles.mjs +3 -3
- package/dist/{stylesheet-writer-TxsinwbO.mjs → stylesheet-writer-BBYjNSMA.mjs} +201 -118
- package/dist/stylesheet-writer-BBYjNSMA.mjs.map +1 -0
- package/dist/{table-BrP0RwUd.mjs → table-BFi2WmgG.mjs} +2 -2
- package/dist/{table-BrP0RwUd.mjs.map → table-BFi2WmgG.mjs.map} +1 -1
- 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/shared-strings.d.ts +2 -0
- package/dist/{workbook-DQCslHzY.mjs → workbook-CMel6UcI.mjs} +13 -12
- package/dist/workbook-CMel6UcI.mjs.map +1 -0
- package/dist/workbook.mjs +2 -2
- package/dist/worksheet/writer.d.ts +9 -5
- package/dist/{worksheet-B2NOQ6dM.mjs → worksheet-Cxn2c-jz.mjs} +3 -3
- package/dist/{worksheet-B2NOQ6dM.mjs.map → worksheet-Cxn2c-jz.mjs.map} +1 -1
- package/dist/worksheet.mjs +2 -2
- package/dist/xml.mjs +2 -2
- package/dist/zip.mjs +1 -1
- package/package.json +1 -1
- package/dist/cell-Df3cKRIl.mjs.map +0 -1
- package/dist/coordinate-Bt5Gni2I.mjs.map +0 -1
- package/dist/escape-rnWSPpWY.mjs.map +0 -1
- package/dist/reader-DHMxLBQV.mjs.map +0 -1
- package/dist/serialize-BC2Wu3bR.mjs.map +0 -1
- package/dist/stylesheet-writer-TxsinwbO.mjs.map +0 -1
- package/dist/workbook-DQCslHzY.mjs.map +0 -1
package/dist/streaming.mjs
CHANGED
|
@@ -1,42 +1,20 @@
|
|
|
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 { n as escapeXmlAttr } from "./escape-
|
|
5
|
-
import { _ as ARC_WORKBOOK_RELS,
|
|
6
|
-
import { t as stylesheetToBytes, v as serializeCell } from "./stylesheet-writer-
|
|
4
|
+
import { a as unescapeCellString, n as escapeXmlAttr } from "./escape-rA5UpibT.mjs";
|
|
5
|
+
import { _ as ARC_WORKBOOK_RELS, c as ARC_CONTENT_TYPES, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, lt as REL_NS, m as ARC_STYLE, p as ARC_SHARED_STRINGS, pt as STYLES_TYPE, ut as SHARED_STRINGS_TYPE, wt as XLSX_TYPE, yt as WORKSHEET_TYPE } from "./tree-BX-sRRVp.mjs";
|
|
6
|
+
import { t as stylesheetToBytes, v as serializeCell, x as parseCellNumber } from "./stylesheet-writer-BBYjNSMA.mjs";
|
|
7
7
|
import { t as parseXml } from "./parser-By6RWZVW.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
8
|
+
import { r as parseXsdBoolean } from "./serialize-Dmb6Vtt9.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-BHUYpjUG.mjs";
|
|
10
|
+
import { ct as buildXfPatch, ht as makeStylesheet, it as addCellXf, lt as defaultCellXf } from "./cell-style-DjKKPWDL.mjs";
|
|
11
|
+
import { A as serializeRichString, D as makeSharedStrings, S as validateSheetTitle, k as parseSharedStringsXml } from "./workbook-CMel6UcI.mjs";
|
|
12
|
+
import { a as parseDate1904, c as resolveRelTarget, l as parseStylesheetXml, n as SHARED_STRINGS_PART, o as parseSheetEntries, r as STYLES_PART, s as readOptionalWorkbookPart, t as OFFICE_DOC_REL_TYPE } from "./load-DIdQ_zum.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
16
|
import { t as iterParse } from "./iterparse-DZE0zy2M.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`;
|
|
@@ -46,16 +24,16 @@ const localName = (qname) => {
|
|
|
46
24
|
const i = qname.lastIndexOf("}");
|
|
47
25
|
return i < 0 ? qname : qname.slice(i + 1);
|
|
48
26
|
};
|
|
49
|
-
const decodeCellValue = (t, vText, inlineText, sst) => {
|
|
27
|
+
const decodeCellValue = (t, vText, inlineText, sst, sheet, col, row) => {
|
|
50
28
|
switch (t) {
|
|
51
|
-
case "n": return vText
|
|
29
|
+
case "n": return parseCellNumber(vText, sheet, col, row);
|
|
52
30
|
case "s": {
|
|
53
31
|
if (vText === void 0) return null;
|
|
54
32
|
const idx = Number.parseInt(vText, 10);
|
|
55
33
|
if (!Number.isInteger(idx) || idx < 0 || idx >= sst.length) return null;
|
|
56
34
|
return sst[idx] ?? null;
|
|
57
35
|
}
|
|
58
|
-
case "b": return vText
|
|
36
|
+
case "b": return parseXsdBoolean(vText) ?? null;
|
|
59
37
|
case "e":
|
|
60
38
|
if (!vText || !ERROR_CODES.has(vText)) return null;
|
|
61
39
|
return {
|
|
@@ -72,14 +50,28 @@ const decodeCellValue = (t, vText, inlineText, sst) => {
|
|
|
72
50
|
* stream that yields them), yielding one `ReadOnlyCell[]` per row that matches
|
|
73
51
|
* `opts`.
|
|
74
52
|
*/
|
|
75
|
-
async function* iterSheetRows(sheetInput, sst, opts) {
|
|
53
|
+
async function* iterSheetRows(title, sheetInput, sst, opts) {
|
|
76
54
|
const minRow = opts.minRow ?? 1;
|
|
77
55
|
const maxRow = opts.maxRow ?? Number.POSITIVE_INFINITY;
|
|
78
56
|
const minCol = opts.minCol ?? 1;
|
|
79
57
|
const maxCol = opts.maxCol ?? Number.POSITIVE_INFINITY;
|
|
80
58
|
let inSheetData = false;
|
|
81
59
|
let currentRow = -1;
|
|
60
|
+
let nextRow = 1;
|
|
82
61
|
let currentCells = [];
|
|
62
|
+
let nextCol = 1;
|
|
63
|
+
let pendingCells = [];
|
|
64
|
+
const settleRow = (row) => {
|
|
65
|
+
currentRow = row;
|
|
66
|
+
nextRow = Math.max(nextRow, row + 1);
|
|
67
|
+
if (row >= minRow && row <= maxRow) for (const cell of pendingCells) currentCells.push({
|
|
68
|
+
row,
|
|
69
|
+
col: cell.col,
|
|
70
|
+
value: decodeCellValue(cell.type, cell.text, cell.inline, sst, title, cell.col, row),
|
|
71
|
+
styleId: cell.styleId
|
|
72
|
+
});
|
|
73
|
+
pendingCells = [];
|
|
74
|
+
};
|
|
83
75
|
let cellOpen = false;
|
|
84
76
|
let cellRow = 0;
|
|
85
77
|
let cellCol = 0;
|
|
@@ -90,6 +82,7 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
90
82
|
let inIs = false;
|
|
91
83
|
let inIsT = false;
|
|
92
84
|
let isText = "";
|
|
85
|
+
let isRunText = "";
|
|
93
86
|
for await (const ev of iterParse(sheetInput)) {
|
|
94
87
|
const e = ev;
|
|
95
88
|
if (e.kind === "start") {
|
|
@@ -100,28 +93,35 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
100
93
|
}
|
|
101
94
|
switch (local) {
|
|
102
95
|
case "row": {
|
|
103
|
-
e.attrs;
|
|
104
96
|
const rRaw = e.attrs["r"];
|
|
105
|
-
|
|
97
|
+
if (rRaw === void 0) currentRow = 0;
|
|
98
|
+
else {
|
|
99
|
+
currentRow = rowNumberFromAttr(rRaw, "loadWorkbookStream");
|
|
100
|
+
nextRow = Math.max(nextRow, currentRow + 1);
|
|
101
|
+
}
|
|
106
102
|
currentCells = [];
|
|
103
|
+
pendingCells = [];
|
|
104
|
+
nextCol = 1;
|
|
107
105
|
break;
|
|
108
106
|
}
|
|
109
107
|
case "c": {
|
|
110
108
|
if (currentRow < 0) break;
|
|
111
|
-
|
|
109
|
+
const ref = e.attrs["r"];
|
|
110
|
+
if (currentRow === 0 && ref) settleRow(coordinateToTuple(ref).row);
|
|
111
|
+
if (currentRow !== 0 && (currentRow < minRow || currentRow > maxRow)) break;
|
|
112
112
|
cellOpen = true;
|
|
113
113
|
cellType = e.attrs["t"] ?? "n";
|
|
114
114
|
const sRaw = e.attrs["s"];
|
|
115
115
|
cellStyleId = sRaw ? Number.parseInt(sRaw, 10) || 0 : 0;
|
|
116
|
-
const ref = e.attrs["r"];
|
|
117
116
|
if (ref) {
|
|
118
117
|
const tup = coordinateToTuple(ref);
|
|
119
118
|
cellRow = tup.row;
|
|
120
119
|
cellCol = tup.col;
|
|
121
120
|
} else {
|
|
122
121
|
cellRow = currentRow;
|
|
123
|
-
cellCol =
|
|
122
|
+
cellCol = nextCol;
|
|
124
123
|
}
|
|
124
|
+
nextCol = cellCol + 1;
|
|
125
125
|
vText = "";
|
|
126
126
|
isText = "";
|
|
127
127
|
break;
|
|
@@ -133,7 +133,10 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
133
133
|
if (cellOpen) inIs = true;
|
|
134
134
|
break;
|
|
135
135
|
case "t":
|
|
136
|
-
if (inIs)
|
|
136
|
+
if (inIs) {
|
|
137
|
+
inIsT = true;
|
|
138
|
+
isRunText = "";
|
|
139
|
+
}
|
|
137
140
|
break;
|
|
138
141
|
default: break;
|
|
139
142
|
}
|
|
@@ -141,7 +144,7 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
141
144
|
}
|
|
142
145
|
if (e.kind === "text") {
|
|
143
146
|
if (inV) vText += e.text;
|
|
144
|
-
else if (inIsT)
|
|
147
|
+
else if (inIsT) isRunText += e.text;
|
|
145
148
|
continue;
|
|
146
149
|
}
|
|
147
150
|
const local = localName(e.name);
|
|
@@ -151,6 +154,7 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
151
154
|
inSheetData = false;
|
|
152
155
|
return;
|
|
153
156
|
case "row":
|
|
157
|
+
if (currentRow === 0) settleRow(derivedRowNumber(nextRow, "loadWorkbookStream"));
|
|
154
158
|
if (currentRow >= minRow && currentRow <= maxRow && currentCells.length > 0) yield currentCells;
|
|
155
159
|
if (currentRow > maxRow) {
|
|
156
160
|
inSheetData = false;
|
|
@@ -160,8 +164,15 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
160
164
|
currentCells = [];
|
|
161
165
|
break;
|
|
162
166
|
case "c":
|
|
163
|
-
if (cellOpen &&
|
|
164
|
-
|
|
167
|
+
if (cellOpen && cellRow === 0 && cellCol >= minCol && cellCol <= maxCol) pendingCells.push({
|
|
168
|
+
col: cellCol,
|
|
169
|
+
type: cellType,
|
|
170
|
+
text: vText,
|
|
171
|
+
inline: isText,
|
|
172
|
+
styleId: cellStyleId
|
|
173
|
+
});
|
|
174
|
+
else if (cellOpen && cellCol >= minCol && cellCol <= maxCol && cellRow >= minRow && cellRow <= maxRow) {
|
|
175
|
+
const value = decodeCellValue(cellType, vText, isText, sst, title, cellCol, cellRow);
|
|
165
176
|
currentCells.push({
|
|
166
177
|
row: cellRow,
|
|
167
178
|
col: cellCol,
|
|
@@ -178,7 +189,10 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
178
189
|
inIs = false;
|
|
179
190
|
break;
|
|
180
191
|
case "t":
|
|
181
|
-
if (
|
|
192
|
+
if (inIsT) {
|
|
193
|
+
isText += unescapeCellString(isRunText);
|
|
194
|
+
inIsT = false;
|
|
195
|
+
}
|
|
182
196
|
break;
|
|
183
197
|
default: break;
|
|
184
198
|
}
|
|
@@ -187,7 +201,9 @@ async function* iterSheetRows(sheetInput, sst, opts) {
|
|
|
187
201
|
const isXmlSpace = (b) => b === 32 || b === 9 || b === 10 || b === 13;
|
|
188
202
|
/**
|
|
189
203
|
* Read the `r="N"` row number out of a `<row …>` attribute region, scanning
|
|
190
|
-
* bytes directly. Returns -1 when the attribute is absent or
|
|
204
|
+
* bytes directly. Returns -1 when the attribute is absent, or holds anything
|
|
205
|
+
* `parseRowNumberAttr` rejects: the two accept the same shapes, so the index
|
|
206
|
+
* can never seek by a number the SAX walk reads differently.
|
|
191
207
|
*
|
|
192
208
|
* Decoding the region to a string and running a regex instead costs one
|
|
193
209
|
* TextDecoder, one string and one match array per row, which on a million-row
|
|
@@ -197,9 +213,11 @@ const readRowAttr = (bytes, from, to) => {
|
|
|
197
213
|
for (let p = from; p + 3 < to; p++) {
|
|
198
214
|
if (!isXmlSpace(bytes[p])) continue;
|
|
199
215
|
if (bytes[p + 1] !== 114 || bytes[p + 2] !== 61 || bytes[p + 3] !== 34) continue;
|
|
216
|
+
let q = p + 4;
|
|
217
|
+
while (q < to && isXmlSpace(bytes[q])) q++;
|
|
218
|
+
if (bytes[q] === 43) q++;
|
|
200
219
|
let value = 0;
|
|
201
220
|
let digits = 0;
|
|
202
|
-
let q = p + 4;
|
|
203
221
|
while (q < to) {
|
|
204
222
|
const d = bytes[q];
|
|
205
223
|
if (d === void 0 || d < 48 || d > 57) break;
|
|
@@ -207,7 +225,8 @@ const readRowAttr = (bytes, from, to) => {
|
|
|
207
225
|
digits++;
|
|
208
226
|
q++;
|
|
209
227
|
}
|
|
210
|
-
|
|
228
|
+
while (q < to && isXmlSpace(bytes[q])) q++;
|
|
229
|
+
if (digits > 0 && value >= 1 && value <= 1048576 && q < to && bytes[q] === 34) return value;
|
|
211
230
|
}
|
|
212
231
|
return -1;
|
|
213
232
|
};
|
|
@@ -270,9 +289,14 @@ const findSheetDataTagEnd = (bytes) => {
|
|
|
270
289
|
* `sheetDataEnd` is the byte offset of `</sheetData>` so callers can clip the
|
|
271
290
|
* region that gets handed to saxes. `sheetDataTagEnd` includes the original
|
|
272
291
|
* namespace-bearing ancestor tags, reused by {@link replayFromRow}.
|
|
292
|
+
*
|
|
293
|
+
* `hasUnnumberedRow` reports a `<row>` this scan cannot number, which makes the
|
|
294
|
+
* whole sheet unseekable: a row without `@r` takes the row its first cell
|
|
295
|
+
* names, and reading cell refs is the SAX walk's job.
|
|
273
296
|
*/
|
|
274
297
|
const buildRowOffsetIndex = (bytes) => {
|
|
275
298
|
const out = [];
|
|
299
|
+
let hasUnnumberedRow = false;
|
|
276
300
|
let sheetDataEnd = -1;
|
|
277
301
|
let i = 0;
|
|
278
302
|
while (i < bytes.length) {
|
|
@@ -302,13 +326,15 @@ const buildRowOffsetIndex = (bytes) => {
|
|
|
302
326
|
row,
|
|
303
327
|
offset: start
|
|
304
328
|
});
|
|
329
|
+
else hasUnnumberedRow = true;
|
|
305
330
|
i = j + 1;
|
|
306
331
|
}
|
|
307
332
|
if (sheetDataEnd < 0) sheetDataEnd = bytes.length;
|
|
308
333
|
return {
|
|
309
334
|
index: out,
|
|
310
335
|
sheetDataEnd,
|
|
311
|
-
sheetDataTagEnd: findSheetDataTagEnd(bytes)
|
|
336
|
+
sheetDataTagEnd: findSheetDataTagEnd(bytes),
|
|
337
|
+
hasUnnumberedRow
|
|
312
338
|
};
|
|
313
339
|
};
|
|
314
340
|
/**
|
|
@@ -358,7 +384,9 @@ const replayFromRow = (bytes, sheetDataTagEnd, fromOffset, sheetDataEnd) => {
|
|
|
358
384
|
* `iterRows({ minRow > 1 })` needs random access instead, so the first band
|
|
359
385
|
* query inflates the part and indexes its row offsets, and holds both for the
|
|
360
386
|
* life of the worksheet handle or until the workbook closes. Subsequent band
|
|
361
|
-
* queries jump straight to the first matching row without inflating or scanning
|
|
387
|
+
* queries jump straight to the first matching row without inflating or scanning
|
|
388
|
+
* again. A sheet whose rows omit `@r` cannot be indexed by row number, so its
|
|
389
|
+
* band queries keep streaming and hold nothing.
|
|
362
390
|
*/
|
|
363
391
|
const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes) => {
|
|
364
392
|
const cacheKey = {};
|
|
@@ -366,9 +394,13 @@ const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes)
|
|
|
366
394
|
let cached = indexes.byWorksheet.get(cacheKey);
|
|
367
395
|
if (!cached) {
|
|
368
396
|
const bytes = archive.read(partPath);
|
|
369
|
-
|
|
397
|
+
const { hasUnnumberedRow, index, sheetDataEnd, sheetDataTagEnd } = buildRowOffsetIndex(bytes);
|
|
398
|
+
cached = hasUnnumberedRow ? { seekable: false } : {
|
|
399
|
+
seekable: true,
|
|
370
400
|
bytes,
|
|
371
|
-
|
|
401
|
+
index,
|
|
402
|
+
sheetDataEnd,
|
|
403
|
+
sheetDataTagEnd
|
|
372
404
|
};
|
|
373
405
|
indexes.byWorksheet.set(cacheKey, cached);
|
|
374
406
|
}
|
|
@@ -376,14 +408,16 @@ const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes)
|
|
|
376
408
|
};
|
|
377
409
|
const iterRows = (opts = {}) => {
|
|
378
410
|
const minRow = opts.minRow ?? 1;
|
|
379
|
-
if (minRow <= 1) return iterSheetRows(archive.readStream(partPath), sst, opts);
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
411
|
+
if (minRow <= 1) return iterSheetRows(title, archive.readStream(partPath), sst, opts);
|
|
412
|
+
const seek = ensureIndexed();
|
|
413
|
+
if (!seek.seekable) return iterSheetRows(title, archive.readStream(partPath), sst, opts);
|
|
414
|
+
const { bytes, index, sheetDataEnd, sheetDataTagEnd } = seek;
|
|
415
|
+
if (index.length === 0 || sheetDataTagEnd < 0) return iterSheetRows(title, bytes, sst, opts);
|
|
382
416
|
const pos = firstRowAtOrAfter(index, minRow);
|
|
383
417
|
if (pos < 0) return (async function* () {})();
|
|
384
418
|
const target = index[pos];
|
|
385
|
-
if (!target) return iterSheetRows(bytes, sst, opts);
|
|
386
|
-
return iterSheetRows(replayFromRow(bytes, sheetDataTagEnd, target.offset, sheetDataEnd), sst, opts);
|
|
419
|
+
if (!target) return iterSheetRows(title, bytes, sst, opts);
|
|
420
|
+
return iterSheetRows(title, replayFromRow(bytes, sheetDataTagEnd, target.offset, sheetDataEnd), sst, opts);
|
|
387
421
|
};
|
|
388
422
|
const iterValues = async function* (opts = {}) {
|
|
389
423
|
for await (const row of iterRows(opts)) yield row.map((c) => c.value);
|
|
@@ -399,16 +433,16 @@ const makeStreamingReadOnlyWorksheet = (title, archive, partPath, sst, indexes)
|
|
|
399
433
|
* sheet list / styles / shared strings. Per the project-wide "no classes" rule
|
|
400
434
|
* (CLAUDE.md), the workbook is a plain object closing over the archive handle.
|
|
401
435
|
*/
|
|
402
|
-
const makeStreamingReadOnlyWorkbook = (sheetNames, styles, date1904, archive,
|
|
436
|
+
const makeStreamingReadOnlyWorkbook = (sheetNames, styles, date1904, archive, partPathByName, sst) => {
|
|
403
437
|
const indexes = { byWorksheet: /* @__PURE__ */ new WeakMap() };
|
|
404
438
|
return {
|
|
405
439
|
sheetNames,
|
|
406
440
|
styles,
|
|
407
441
|
date1904,
|
|
408
442
|
openWorksheet(name) {
|
|
409
|
-
const
|
|
410
|
-
if (
|
|
411
|
-
return makeStreamingReadOnlyWorksheet(name, archive,
|
|
443
|
+
const partPath = partPathByName.get(name);
|
|
444
|
+
if (partPath === void 0) throw new OpenXmlSchemaError(`loadWorkbookStream: no worksheet named "${name}"`);
|
|
445
|
+
return makeStreamingReadOnlyWorksheet(name, archive, partPath, sst, indexes);
|
|
412
446
|
},
|
|
413
447
|
async close() {
|
|
414
448
|
archive.close();
|
|
@@ -422,21 +456,94 @@ async function loadWorkbookStream(source, opts = {}) {
|
|
|
422
456
|
if (!archive.has("[Content_Types].xml")) throw new OpenXmlSchemaError(`loadWorkbookStream: missing "${ARC_CONTENT_TYPES}"`);
|
|
423
457
|
manifestFromBytes(archive.read(ARC_CONTENT_TYPES));
|
|
424
458
|
if (!archive.has(ARC_ROOT_RELS)) throw new OpenXmlSchemaError(`loadWorkbookStream: missing "${ARC_ROOT_RELS}"`);
|
|
425
|
-
const officeDocRel = relsFromBytes(archive.read(ARC_ROOT_RELS))
|
|
459
|
+
const officeDocRel = findByType(relsFromBytes(archive.read(ARC_ROOT_RELS)), OFFICE_DOC_REL_TYPE);
|
|
426
460
|
if (!officeDocRel) throw new OpenXmlSchemaError(`loadWorkbookStream: no officeDocument relationship in root rels`);
|
|
427
461
|
const workbookPath = resolveRelTarget("", officeDocRel.target);
|
|
428
462
|
if (!archive.has(workbookPath)) throw new OpenXmlSchemaError(`loadWorkbookStream: workbook part "${workbookPath}" missing`);
|
|
429
463
|
const workbookRoot = parseXml(archive.read(workbookPath));
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
464
|
+
const declaredSheets = parseSheetEntries(workbookRoot);
|
|
465
|
+
const wbRelsPath = relsPathFor(workbookPath);
|
|
466
|
+
if (declaredSheets.length > 0 && !archive.has(wbRelsPath)) throw new OpenXmlSchemaError(`loadWorkbookStream: workbook has sheets but rels part "${wbRelsPath}" is missing`);
|
|
467
|
+
const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : makeRelationships();
|
|
468
|
+
const partPathByName = /* @__PURE__ */ new Map();
|
|
469
|
+
for (const declared of declaredSheets) {
|
|
470
|
+
if (partPathByName.has(declared.name)) throw new OpenXmlSchemaError(`loadWorkbookStream: duplicate sheet name "${declared.name}"`);
|
|
471
|
+
const rel = findById(wbRels, declared.rId);
|
|
472
|
+
if (!rel) throw new OpenXmlSchemaError(`loadWorkbookStream: sheet "${declared.name}" rId "${declared.rId}" has no matching rels entry`);
|
|
473
|
+
partPathByName.set(declared.name, resolveRelTarget(workbookPath, rel.target));
|
|
474
|
+
}
|
|
475
|
+
const sstBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, SHARED_STRINGS_PART);
|
|
476
|
+
const sst = sstBytes === void 0 ? makeSharedStrings() : parseSharedStringsXml(sstBytes);
|
|
477
|
+
const stylesBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, STYLES_PART);
|
|
478
|
+
const styles = stylesBytes === void 0 ? makeStylesheet() : parseStylesheetXml(stylesBytes);
|
|
479
|
+
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("")));
|
|
438
480
|
}
|
|
439
481
|
//#endregion
|
|
482
|
+
//#region src/streaming/string-table.ts
|
|
483
|
+
const MAX_ENTRIES = 1e5;
|
|
484
|
+
const MAX_PAYLOAD_BYTES = 8 * 1024 * 1024;
|
|
485
|
+
const UTF16_BYTES_PER_UNIT = 2;
|
|
486
|
+
const XML_CHUNK_UNITS = 16 * 1024;
|
|
487
|
+
const createWriteOnlyStringTable = () => {
|
|
488
|
+
const entries = [];
|
|
489
|
+
const plainIndex = /* @__PURE__ */ new Map();
|
|
490
|
+
const richIndex = /* @__PURE__ */ new Map();
|
|
491
|
+
let payloadBytes = 0;
|
|
492
|
+
let full = false;
|
|
493
|
+
const serialize = (value) => {
|
|
494
|
+
const plain = typeof value === "string";
|
|
495
|
+
const key = plain ? value : serializeRichString(value);
|
|
496
|
+
const index = plain ? plainIndex : richIndex;
|
|
497
|
+
const cached = index.get(key);
|
|
498
|
+
if (cached !== void 0) return {
|
|
499
|
+
type: "s",
|
|
500
|
+
xml: `<v>${cached}</v>`
|
|
501
|
+
};
|
|
502
|
+
const body = plain ? serializeRichString(value) : key;
|
|
503
|
+
const bytes = (key.length + body.length) * UTF16_BYTES_PER_UNIT;
|
|
504
|
+
if (!full && entries.length < MAX_ENTRIES && payloadBytes + bytes <= MAX_PAYLOAD_BYTES) {
|
|
505
|
+
const id = entries.length;
|
|
506
|
+
entries.push(body);
|
|
507
|
+
index.set(key, id);
|
|
508
|
+
payloadBytes += bytes;
|
|
509
|
+
return {
|
|
510
|
+
type: "s",
|
|
511
|
+
xml: `<v>${id}</v>`
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
full = true;
|
|
515
|
+
return {
|
|
516
|
+
type: "inlineStr",
|
|
517
|
+
xml: `<is>${body}</is>`
|
|
518
|
+
};
|
|
519
|
+
};
|
|
520
|
+
const write = async (stream) => {
|
|
521
|
+
const encoder = new TextEncoder();
|
|
522
|
+
let pending = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><sst xmlns="${SHEET_MAIN_NS}" count="${entries.length}" uniqueCount="${entries.length}">`;
|
|
523
|
+
const append = (text) => {
|
|
524
|
+
pending += text;
|
|
525
|
+
while (pending.length >= XML_CHUNK_UNITS) {
|
|
526
|
+
let end = XML_CHUNK_UNITS;
|
|
527
|
+
const last = pending.charCodeAt(end - 1);
|
|
528
|
+
if (last >= 55296 && last <= 56319) end--;
|
|
529
|
+
stream.write(encoder.encode(pending.slice(0, end)));
|
|
530
|
+
pending = pending.slice(end);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
for (const body of entries) append(`<si>${body}</si>`);
|
|
534
|
+
append("</sst>");
|
|
535
|
+
if (pending.length > 0) stream.write(encoder.encode(pending));
|
|
536
|
+
await stream.end();
|
|
537
|
+
};
|
|
538
|
+
return {
|
|
539
|
+
serialize,
|
|
540
|
+
write,
|
|
541
|
+
get size() {
|
|
542
|
+
return entries.length;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
//#endregion
|
|
440
547
|
//#region src/streaming/write-only.ts
|
|
441
548
|
const escapeAttr = escapeXmlAttr;
|
|
442
549
|
const validateTitle = (title, taken) => {
|
|
@@ -467,10 +574,10 @@ const FLUSH_THRESHOLD_BYTES = 64 * 1024;
|
|
|
467
574
|
* closure state.
|
|
468
575
|
*
|
|
469
576
|
* The worksheet streams its `<sheetData>` body chunk-by-chunk through the ZIP
|
|
470
|
-
* writer's `addStreamingEntry` API
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
577
|
+
* writer's `addStreamingEntry` API. Row buffering stays at ~64 KB plus the
|
|
578
|
+
* current row and deflate scratch; strings have a separate workbook-wide cap.
|
|
579
|
+
* The XML envelope (decl / worksheet open / cols / sheetData open) flushes on
|
|
580
|
+
* the first `appendRow` (or `close()` if the sheet is empty);
|
|
474
581
|
* column widths staged via `setColumnWidth` *must* land before the first row.
|
|
475
582
|
*/
|
|
476
583
|
const makeWriteOnlyWorksheet = (state, title, sheetId) => {
|
|
@@ -479,9 +586,8 @@ const makeWriteOnlyWorksheet = (state, title, sheetId) => {
|
|
|
479
586
|
let headerFlushed = false;
|
|
480
587
|
const columnWidths = /* @__PURE__ */ new Map();
|
|
481
588
|
const dummyCtx = {
|
|
482
|
-
sharedStrings:
|
|
483
|
-
styles: state.styles
|
|
484
|
-
rels: makeRelationships()
|
|
589
|
+
sharedStrings: makeSharedStrings(),
|
|
590
|
+
styles: state.styles
|
|
485
591
|
};
|
|
486
592
|
const encoder = new TextEncoder();
|
|
487
593
|
const stream = state.writer.addStreamingEntry(`xl/worksheets/sheet${sheetId}.xml`);
|
|
@@ -532,7 +638,7 @@ const makeWriteOnlyWorksheet = (state, title, sheetId) => {
|
|
|
532
638
|
col,
|
|
533
639
|
value,
|
|
534
640
|
styleId
|
|
535
|
-
}, dummyCtx);
|
|
641
|
+
}, dummyCtx, state.strings.serialize);
|
|
536
642
|
}
|
|
537
643
|
xml += "</row>";
|
|
538
644
|
writeText(xml);
|
|
@@ -576,7 +682,7 @@ const makeWriteOnlyWorkbook = (sink, zipOpts) => {
|
|
|
576
682
|
addCellXf(styles, defaultCellXf());
|
|
577
683
|
const state = {
|
|
578
684
|
styles,
|
|
579
|
-
|
|
685
|
+
strings: createWriteOnlyStringTable(),
|
|
580
686
|
sheets: [],
|
|
581
687
|
finalised: false,
|
|
582
688
|
hasOpenWorksheet: false,
|
|
@@ -616,7 +722,7 @@ const makeWriteOnlyWorkbook = (sink, zipOpts) => {
|
|
|
616
722
|
};
|
|
617
723
|
async function finalizeImpl(state, writer) {
|
|
618
724
|
await writer.addEntry(ARC_STYLE, stylesheetToBytes(state.styles));
|
|
619
|
-
if (state.
|
|
725
|
+
if (state.strings.size > 0) await state.strings.write(writer.addStreamingEntry(ARC_SHARED_STRINGS));
|
|
620
726
|
const workbookXml = serializeWorkbookXml(state.sheets);
|
|
621
727
|
await writer.addEntry(ARC_WORKBOOK, new TextEncoder().encode(workbookXml));
|
|
622
728
|
const wbRels = makeRelationships();
|
|
@@ -627,7 +733,7 @@ async function finalizeImpl(state, writer) {
|
|
|
627
733
|
target: `worksheets/sheet${s.sheetId}.xml`
|
|
628
734
|
});
|
|
629
735
|
});
|
|
630
|
-
if (state.
|
|
736
|
+
if (state.strings.size > 0) wbRels.rels.push({
|
|
631
737
|
id: `rId${wbRels.rels.length + 1}`,
|
|
632
738
|
type: `${REL_NS}/sharedStrings`,
|
|
633
739
|
target: "sharedStrings.xml"
|
|
@@ -651,7 +757,7 @@ async function finalizeImpl(state, writer) {
|
|
|
651
757
|
addOverride(manifest, `/${ARC_WORKBOOK}`, XLSX_TYPE);
|
|
652
758
|
for (const s of state.sheets) addOverride(manifest, `/xl/worksheets/sheet${s.sheetId}.xml`, WORKSHEET_TYPE);
|
|
653
759
|
addOverride(manifest, `/${ARC_STYLE}`, STYLES_TYPE);
|
|
654
|
-
if (state.
|
|
760
|
+
if (state.strings.size > 0) addOverride(manifest, `/${ARC_SHARED_STRINGS}`, SHARED_STRINGS_TYPE);
|
|
655
761
|
await writer.addEntry(ARC_CONTENT_TYPES, manifestToBytes(manifest));
|
|
656
762
|
await writer.finalize();
|
|
657
763
|
}
|