@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.
Files changed (87) hide show
  1. package/README.md +89 -4
  2. package/dist/cell/cell.d.ts +12 -19
  3. package/dist/{cell-Df3cKRIl.mjs → cell-B5OUUdfI.mjs} +13 -7
  4. package/dist/cell-B5OUUdfI.mjs.map +1 -0
  5. package/dist/{cell-style-CNsET6WU.mjs → cell-style-CbB4nXTY.mjs} +11 -4
  6. package/dist/cell-style-CbB4nXTY.mjs.map +1 -0
  7. package/dist/cell.mjs +1 -1
  8. package/dist/{coordinate-Bt5Gni2I.mjs → coordinate-CxC88uBk.mjs} +31 -2
  9. package/dist/coordinate-CxC88uBk.mjs.map +1 -0
  10. package/dist/{defined-names-Ctu3F6ls.mjs → defined-names-Cmp8rbvI.mjs} +2 -2
  11. package/dist/{defined-names-Ctu3F6ls.mjs.map → defined-names-Cmp8rbvI.mjs.map} +1 -1
  12. package/dist/{differential-BPKa7wXi.mjs → differential-QLaKEiBk.mjs} +2 -2
  13. package/dist/{differential-BPKa7wXi.mjs.map → differential-QLaKEiBk.mjs.map} +1 -1
  14. package/dist/{drawing-ZJ3h4VHD.mjs → drawing-Cv6UtInY.mjs} +2 -2
  15. package/dist/{drawing-ZJ3h4VHD.mjs.map → drawing-Cv6UtInY.mjs.map} +1 -1
  16. package/dist/drawing.mjs +1 -1
  17. package/dist/{escape-rnWSPpWY.mjs → escape-rA5UpibT.mjs} +26 -3
  18. package/dist/escape-rA5UpibT.mjs.map +1 -0
  19. package/dist/io/load.d.ts +30 -0
  20. package/dist/io.mjs +2 -2
  21. package/dist/{iterparse-DZE0zy2M.mjs → iterparse-CZoRXvZW.mjs} +2 -2
  22. package/dist/{iterparse-DZE0zy2M.mjs.map → iterparse-CZoRXvZW.mjs.map} +1 -1
  23. package/dist/{load-B1sK-LjT.mjs → load-BpnCPv1S.mjs} +84 -45
  24. package/dist/load-BpnCPv1S.mjs.map +1 -0
  25. package/dist/{manifest-pLmx7KYh.mjs → manifest-9p0ccqmR.mjs} +10 -11
  26. package/dist/manifest-9p0ccqmR.mjs.map +1 -0
  27. package/dist/node.mjs +1 -1
  28. package/dist/packaging.mjs +1 -1
  29. package/dist/{parser-By6RWZVW.mjs → parser-B1tB9YaE.mjs} +15 -5
  30. package/dist/parser-B1tB9YaE.mjs.map +1 -0
  31. package/dist/{reader-DHMxLBQV.mjs → reader-pC7bxYAs.mjs} +30 -7
  32. package/dist/reader-pC7bxYAs.mjs.map +1 -0
  33. package/dist/{save-DmdWwxmw.mjs → save-1Z3344nz.mjs} +8 -8
  34. package/dist/{save-DmdWwxmw.mjs.map → save-1Z3344nz.mjs.map} +1 -1
  35. package/dist/schema.mjs +1 -1
  36. package/dist/{serialize-BC2Wu3bR.mjs → serialize-BtxgssFK.mjs} +24 -7
  37. package/dist/serialize-BtxgssFK.mjs.map +1 -0
  38. package/dist/{serializer-C6nm0mRJ.mjs → serializer-N6XIwsgp.mjs} +3 -3
  39. package/dist/{serializer-C6nm0mRJ.mjs.map → serializer-N6XIwsgp.mjs.map} +1 -1
  40. package/dist/streaming.mjs +129 -77
  41. package/dist/streaming.mjs.map +1 -1
  42. package/dist/styles/display-text.d.ts +95 -0
  43. package/dist/styles/format-code.d.ts +125 -0
  44. package/dist/styles/index.d.ts +1 -0
  45. package/dist/styles/numbers.d.ts +7 -0
  46. package/dist/styles.mjs +1161 -4
  47. package/dist/styles.mjs.map +1 -0
  48. package/dist/{stylesheet-writer-oXSuGnJy.mjs → stylesheet-writer-BEn-0P0x.mjs} +480 -173
  49. package/dist/stylesheet-writer-BEn-0P0x.mjs.map +1 -0
  50. package/dist/{table-BrP0RwUd.mjs → table-BxOLUZs3.mjs} +2 -2
  51. package/dist/{table-BrP0RwUd.mjs.map → table-BxOLUZs3.mjs.map} +1 -1
  52. package/dist/{tree-BX-sRRVp.mjs → tree-CspNjuYl.mjs} +12 -2
  53. package/dist/tree-CspNjuYl.mjs.map +1 -0
  54. package/dist/utils/cell-number.d.ts +10 -0
  55. package/dist/utils/coordinate.d.ts +11 -0
  56. package/dist/utils/escape.d.ts +17 -0
  57. package/dist/utils/xsd-boolean.d.ts +9 -0
  58. package/dist/utils.mjs +2 -2
  59. package/dist/workbook/index.d.ts +1 -1
  60. package/dist/workbook/workbook.d.ts +9 -46
  61. package/dist/{workbook-DOTFzCpz.mjs → workbook-tG9oYtzU.mjs} +50 -13
  62. package/dist/workbook-tG9oYtzU.mjs.map +1 -0
  63. package/dist/workbook.mjs +3 -3
  64. package/dist/worksheet/index.d.ts +1 -1
  65. package/dist/worksheet/worksheet.d.ts +55 -14
  66. package/dist/{worksheet-B2NOQ6dM.mjs → worksheet-DLMyDD7W.mjs} +87 -22
  67. package/dist/worksheet-DLMyDD7W.mjs.map +1 -0
  68. package/dist/worksheet.mjs +3 -3
  69. package/dist/xml/namespaces.d.ts +7 -0
  70. package/dist/xml/parser.d.ts +12 -0
  71. package/dist/xml/strict-package.d.ts +14 -0
  72. package/dist/xml.mjs +5 -5
  73. package/dist/zip.mjs +1 -1
  74. package/package.json +1 -1
  75. package/dist/cell-Df3cKRIl.mjs.map +0 -1
  76. package/dist/cell-style-CNsET6WU.mjs.map +0 -1
  77. package/dist/coordinate-Bt5Gni2I.mjs.map +0 -1
  78. package/dist/escape-rnWSPpWY.mjs.map +0 -1
  79. package/dist/load-B1sK-LjT.mjs.map +0 -1
  80. package/dist/manifest-pLmx7KYh.mjs.map +0 -1
  81. package/dist/parser-By6RWZVW.mjs.map +0 -1
  82. package/dist/reader-DHMxLBQV.mjs.map +0 -1
  83. package/dist/serialize-BC2Wu3bR.mjs.map +0 -1
  84. package/dist/stylesheet-writer-oXSuGnJy.mjs.map +0 -1
  85. package/dist/tree-BX-sRRVp.mjs.map +0 -1
  86. package/dist/workbook-DOTFzCpz.mjs.map +0 -1
  87. package/dist/worksheet-B2NOQ6dM.mjs.map +0 -1
@@ -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-Bt5Gni2I.mjs";
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 { i as unescapeCellString, n as escapeXmlAttr } from "./escape-rnWSPpWY.mjs";
5
- import { _ as ARC_WORKBOOK_RELS, a as findChildren, c as ARC_CONTENT_TYPES, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, i as findChild, 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 } from "./stylesheet-writer-oXSuGnJy.mjs";
7
- import { t as parseXml } from "./parser-By6RWZVW.mjs";
8
- import { 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-pLmx7KYh.mjs";
9
- import { ct as buildXfPatch, ht as makeStylesheet, it as addCellXf, lt as defaultCellXf } from "./cell-style-CNsET6WU.mjs";
10
- import { A as serializeRichString, D as makeSharedStrings, S as validateSheetTitle, k as parseSharedStringsXml } from "./workbook-DOTFzCpz.mjs";
11
- import { i as parseStylesheetXml, n as parseDate1904, r as resolveRelTarget } from "./load-B1sK-LjT.mjs";
12
- import { t as openZip } from "./reader-DHMxLBQV.mjs";
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-DZE0zy2M.mjs";
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 localName = (qname) => {
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 !== void 0 && vText !== "" ? Number.parseFloat(vText) : null;
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 === "1";
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
- const local = localName(e.name);
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
- currentRow = rRaw ? Number.parseInt(rRaw, 10) : currentRow + 1;
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
- if (currentRow < minRow || currentRow > maxRow) break;
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 = (currentCells[currentCells.length - 1]?.col ?? 0) + 1;
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 = localName(e.name);
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 && cellCol >= minCol && cellCol <= maxCol && cellRow >= minRow && cellRow <= maxRow) {
168
- const value = decodeCellValue(cellType, vText, isText, sst);
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 malformed.
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
- if (digits > 0 && q < to && bytes[q] === 34) return value;
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 again.
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
- cached = {
406
+ const { hasUnnumberedRow, index, sheetDataEnd, sheetDataTagEnd } = buildRowOffsetIndex(bytes);
407
+ cached = hasUnnumberedRow ? { seekable: false } : {
408
+ seekable: true,
377
409
  bytes,
378
- ...buildRowOffsetIndex(bytes)
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 { bytes, index, sheetDataEnd, sheetDataTagEnd } = ensureIndexed();
388
- if (index.length === 0 || sheetDataTagEnd < 0) return iterSheetRows(bytes, sst, opts);
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, entries, sst) => {
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 entry = entries.get(name);
417
- if (!entry) throw new OpenXmlSchemaError(`loadWorkbookStream: no worksheet named "${name}"`);
418
- return makeStreamingReadOnlyWorksheet(name, archive, entry.partPath, sst, indexes);
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 officeDocRel = relsFromBytes(archive.read(ARC_ROOT_RELS)).rels.find((r) => r.type === `${REL_NS}/officeDocument`);
433
- if (!officeDocRel) throw new OpenXmlSchemaError(`loadWorkbookStream: no officeDocument relationship in root rels`);
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
- const sheetEntries = parseSheetList(workbookRoot, workbookPath, archive);
438
- const entryMap = /* @__PURE__ */ new Map();
439
- for (const e of sheetEntries) entryMap.set(e.name, e);
440
- let sst = makeSharedStrings();
441
- if (archive.has(ARC_SHARED_STRINGS)) sst = parseSharedStringsXml(archive.read(ARC_SHARED_STRINGS));
442
- let styles = makeStylesheet();
443
- if (archive.has(ARC_STYLE)) styles = parseStylesheetXml(archive.read(ARC_STYLE));
444
- return makeStreamingReadOnlyWorkbook(sheetEntries.map((e) => e.name), styles, parseDate1904(workbookRoot), archive, entryMap, sst.entries.map((e) => typeof e === "string" ? e : e.runs.map((r) => r.text).join("")));
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