xlvane 1.1.0 → 1.1.1

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.
@@ -1,4 +1,4 @@
1
- import { $ as fillXml, At as EMPTY_STYLES, B as tokenize, Bt as suggest, Cn as escapeText, Ct as relationshipsPartName, En as XlvError, Ft as normalizeStyle, G as DEFAULT_FONT, H as X14_DV_URI, I as WarningCollector, It as stableKey, J as borderXml, Lt as isDateFormat, M as openZip, Mt as READING_ORDERS, Nt as checkStyleCount, Pt as mergeStyles, Rt as DEFAULT_READ_LIMITS, Sn as escapeAttr, St as openPackage, Tt as zipNameToPartName, V as X14_CF_URI, _ as isoDate, an as columnLetters, at as reusableBuiltinNumFmt, b as readWorkbook, bn as parseXml, bt as parseIsoDateTime, cn as parseCellRef, en as SML_NS, et as fontXml, f as createWorkbook, ft as stringItemXml, gn as parseXmlTree, h as detectHeader, hn as childrenNamed, i as readOptions, in as MAX_ROWS, jt as parseStyles, ln as parseRange, m as writeWorkbook, mn as childNamed, mt as textElement, n as oneOfOption, nn as relationshipTypes, on as formatCellRef, q as attr, r as openInput, rn as MAX_COLUMNS, sn as formatRange, t as booleanOption, tn as classifyRelationship, tt as formatNumber, vn as decodeXml, vt as writeZip, wt as partKey, x as toBytes, xn as encodeXstring, xt as parseSharedStrings, yn as normalizeLineEnds, z as shiftFormula, zt as resolveLimits } from "./options-CwSp2pA4.js";
1
+ import { $ as fillXml, At as EMPTY_STYLES, B as tokenize, Bt as resolveLimits, Cn as encodeXstring, Ct as relationshipsPartName, Ft as normalizeStyle, G as DEFAULT_FONT, H as X14_DV_URI, I as WarningCollector, It as stableKey, J as borderXml, Lt as isDateFormat, M as openZip, Mt as READING_ORDERS, Nt as checkStyleCount, On as XlvError, Pt as mergeStyles, Rt as DEFAULT_READ_LIMITS, Sn as parseXml, St as openPackage, Tn as escapeText, Tt as zipNameToPartName, V as X14_CF_URI, Vt as suggest, _ as isoDate, _n as childrenNamed, an as MAX_COLUMNS, at as reusableBuiltinNumFmt, b as readWorkbook, bn as decodeXml, bt as parseIsoDateTime, cn as formatCellRef, dn as parseRange, et as fontXml, f as createWorkbook, ft as stringItemXml, gn as childNamed, h as detectHeader, i as readOptions, in as relationshipTypes, jt as parseStyles, ln as formatRange, m as writeWorkbook, mt as textElement, n as oneOfOption, nn as SML_NS, on as MAX_ROWS, q as attr, r as openInput, rn as classifyRelationship, sn as columnLetters, t as booleanOption, tn as REL_NS, tt as formatNumber, un as parseCellRef, vn as parseXmlTree, vt as writeZip, wn as escapeAttr, wt as partKey, x as toBytes, xn as normalizeLineEnds, xt as parseSharedStrings, z as shiftFormula, zt as checkOutputPositions } from "./options-Ba6r4zP1.js";
2
2
  // src/table.ts
3
3
  const READ_OPTIONS = [
4
4
  "sheet",
@@ -106,6 +106,7 @@ function pickTable(workbook, o) {
106
106
  }
107
107
  function prepare(input, options) {
108
108
  const o = readOptions(options, READ_OPTIONS, "readTable options");
109
+ const limits = resolveLimits(o.get("limits"));
109
110
  const workbook = openInput(input, o.get("limits"), o.get("onWarning"));
110
111
  const table = pickTable(workbook, o);
111
112
  const sheet = table?.sheet ?? pickSheet(workbook, o.get("sheet"));
@@ -173,13 +174,16 @@ function prepare(input, options) {
173
174
  }
174
175
  const columns = [];
175
176
  const used = /* @__PURE__ */ new Set();
177
+ const nextSuffix = /* @__PURE__ */ new Map();
176
178
  const headerCells = headerRow === null ? void 0 : byRow.get(headerRow);
177
179
  for (let col = minCol; col <= maxCol; col++) {
178
180
  const letter = columnLetters(col);
179
181
  const header = headerRow === null ? null : headerText(headerCells?.get(col));
180
182
  const base = header === null || header.trim() === "" ? letter : header.trim();
181
183
  let key = base;
182
- for (let n = 2; used.has(key); n++) key = `${base}_${String(n)}`;
184
+ let n = nextSuffix.get(base) ?? 2;
185
+ while (used.has(key)) key = `${base}_${String(n++)}`;
186
+ nextSuffix.set(base, n);
183
187
  used.add(key);
184
188
  columns.push({
185
189
  key,
@@ -192,6 +196,9 @@ function prepare(input, options) {
192
196
  const rowNumbers = [];
193
197
  const first = headerRow === null ? minRow : headerRow + 1;
194
198
  const numbers = skipEmpty ? [...byRow.keys()].filter((r) => r >= first && r <= maxRow).sort((a, b) => a - b) : rangeOf(first, maxRow);
199
+ let cellCount = 0;
200
+ for (const cells of byRow.values()) cellCount += cells.size;
201
+ checkOutputPositions(numbers.length * columns.length, cellCount, limits.maxCells, "The table", sheet.name);
195
202
  for (const r of numbers) {
196
203
  const cells = byRow.get(r);
197
204
  if (skipEmpty && (cells === void 0 || [...cells.values()].every((c) => c.value === null))) continue;
@@ -549,7 +556,11 @@ function fromJSON(input, options) {
549
556
  return writeTable(rows, options);
550
557
  }
551
558
  // src/csv.ts
552
- /** First characters that make spreadsheet applications treat a CSV field as a formula (CSV-10). */
559
+ /**
560
+ * First characters that make spreadsheet applications treat a CSV field as a formula (CSV-10),
561
+ * plus, as a precaution, the small forms of = + - @ (CSV-12, D-H01). Text starting with any other
562
+ * C0 control character is escaped too (see `injectionStart`).
563
+ */
553
564
  const INJECTION_START = /* @__PURE__ */ new Set([
554
565
  "=",
555
566
  "+",
@@ -561,8 +572,14 @@ const INJECTION_START = /* @__PURE__ */ new Set([
561
572
  "=",
562
573
  "+",
563
574
  "-",
564
- "@"
575
+ "@",
576
+ "﹦",
577
+ "﹢",
578
+ "﹣",
579
+ "﹫"
565
580
  ]);
581
+ /** Whether text starting with `c` is escaped: one of INJECTION_START or a C0 control (NUL included). */
582
+ const injectionStart = (c) => INJECTION_START.has(c) || c.charCodeAt(0) < 32;
566
583
  /**
567
584
  * Converts a sheet to CSV text. Fields that contain the delimiter, a quote, CR or LF are
568
585
  * enclosed in quotes, and quotes are doubled (RFC 4180). Numbers are written in JavaScript's
@@ -598,6 +615,7 @@ function toCSV(input, options) {
598
615
  const newline = oneOfOption("newline", o.get("newline"), ["\r\n", "\n"], "\r\n");
599
616
  const bom = booleanOption("bom", o.get("bom"), false);
600
617
  const injection = oneOfOption("formulaInjection", o.get("formulaInjection"), ["escape", "off"], "escape");
618
+ const limits = resolveLimits(o.get("limits"));
601
619
  const sheet = pickSheet(openInput(input, o.get("limits"), o.get("onWarning")), o.get("sheet"));
602
620
  let startRow = 1;
603
621
  let startCol = 1;
@@ -617,6 +635,16 @@ function toCSV(input, options) {
617
635
  if (cell.row > endRow) endRow = cell.row;
618
636
  if (cell.col > endCol) endCol = cell.col;
619
637
  }
638
+ const byRow = /* @__PURE__ */ new Map();
639
+ let cellCount = 0;
640
+ for (const cell of sheet.cells()) {
641
+ if (cell.row < startRow || cell.row > endRow || cell.col < startCol || cell.col > endCol) continue;
642
+ let r = byRow.get(cell.row);
643
+ if (r === void 0) byRow.set(cell.row, r = /* @__PURE__ */ new Map());
644
+ r.set(cell.col, cell.value);
645
+ cellCount++;
646
+ }
647
+ if (endRow >= startRow && endCol >= startCol) checkOutputPositions((endRow - startRow + 1) * (endCol - startCol + 1), cellCount, limits.maxCells, "The CSV text", sheet.name);
620
648
  const needsQuotes = (s) => s.includes(delimiter) || s.includes("\"") || s.includes("\r") || s.includes("\n");
621
649
  const field = (value) => {
622
650
  if (value === null || value === void 0) return "";
@@ -625,7 +653,7 @@ function toCSV(input, options) {
625
653
  if (value instanceof Date) return isoDate(value);
626
654
  let s = String(value);
627
655
  let quote = needsQuotes(s);
628
- if (injection === "escape" && s.length > 0 && INJECTION_START.has(s[0] ?? "")) {
656
+ if (injection === "escape" && s.length > 0 && injectionStart(s[0] ?? "")) {
629
657
  s = `'${s}`;
630
658
  quote = true;
631
659
  }
@@ -634,10 +662,8 @@ function toCSV(input, options) {
634
662
  const lines = [];
635
663
  for (let r = startRow; r <= endRow; r++) {
636
664
  const fields = [];
637
- for (let c = startCol; c <= endCol; c++) {
638
- const cell = sheet.cell(formatCellRef(r, c));
639
- fields.push(field(cell?.value));
640
- }
665
+ const cells = byRow.get(r);
666
+ for (let c = startCol; c <= endCol; c++) fields.push(field(cells?.get(c)));
641
667
  lines.push(fields.join(delimiter));
642
668
  }
643
669
  const body = lines.length === 0 ? "" : lines.join(newline) + newline;
@@ -3255,8 +3281,25 @@ function eachVmlShape(text, change) {
3255
3281
  out.push(text.slice(pos));
3256
3282
  return out.join("");
3257
3283
  }
3284
+ /**
3285
+ * Finds the first `drawing` and `legacyDrawing` elements of the worksheet (SpreadsheetML
3286
+ * namespace, any depth, so also inside `mc:AlternateContent`) with the element scanner, so that
3287
+ * comments are skipped and the time is linear in the part's size (D-H01); the id is the
3288
+ * attribute `id` in the relationships namespace, whatever its prefix.
3289
+ */
3290
+ function drawingIds(text, part, limits) {
3291
+ const { elements } = scan(text, part, limits, (local, depth, _parent, ns) => depth >= 2 && (local === "drawing" || local === "legacyDrawing") && (ns === SML_NS.transitional || ns === SML_NS.strict));
3292
+ const idOf = (local) => {
3293
+ const e = elements.find((x) => x.local === local);
3294
+ return e?.attrs.get(`{${REL_NS.transitional}}id`) ?? e?.attrs.get(`{${REL_NS.strict}}id`);
3295
+ };
3296
+ return {
3297
+ drawing: idOf("drawing"),
3298
+ legacyDrawing: idOf("legacyDrawing")
3299
+ };
3300
+ }
3258
3301
  /** Rewrites the notes of the changed sheet: the Comments part and the note shapes of its VML part. */
3259
- function rewriteNotes(edit, pkg, sheetPart, sheetText, sheet, c) {
3302
+ function rewriteNotes(edit, pkg, sheetPart, ids, sheet, c) {
3260
3303
  const rels = pkg.relationships(sheetPart);
3261
3304
  const comments = rels.find((r) => !r.external && classifyRelationship(r.type)?.name === "comments");
3262
3305
  if (comments !== void 0) {
@@ -3282,7 +3325,7 @@ function rewriteNotes(edit, pkg, sheetPart, sheetText, sheet, c) {
3282
3325
  if (edits.length > 0) edit.write(comments.target, applyEdits(text, edits));
3283
3326
  }
3284
3327
  }
3285
- const id = /<(?:[A-Za-z_][\w.-]*:)?legacyDrawing\b[^>]*?\br:id\s*=\s*["']([^"']*)["']/.exec(sheetText)?.[1];
3328
+ const id = ids.legacyDrawing;
3286
3329
  const vml = id === void 0 ? void 0 : rels.find((r) => r.id === id && !r.external);
3287
3330
  if (vml === void 0) return;
3288
3331
  const text = edit.text(vml.target);
@@ -3318,8 +3361,8 @@ function rewriteNotes(edit, pkg, sheetPart, sheetText, sheet, c) {
3318
3361
  * rows. Drawings with charts, shapes, groups or other objects are refused, and so is a deletion
3319
3362
  * that would cut a picture (a deleted row inside the rows it covers or under its anchor cell).
3320
3363
  */
3321
- function rewriteDrawing(edit, pkg, sheetPart, sheetText, sheet, c) {
3322
- const id = /<(?:[A-Za-z_][\w.-]*:)?drawing\b[^>]*?\br:id\s*=\s*["']([^"']*)["']/.exec(sheetText)?.[1];
3364
+ function rewriteDrawing(edit, pkg, sheetPart, ids, sheet, c) {
3365
+ const id = ids.drawing;
3323
3366
  const rels = pkg.relationships(sheetPart);
3324
3367
  const rel = id === void 0 ? void 0 : rels.find((r) => r.id === id && !r.external && classifyRelationship(r.type)?.name === "drawing");
3325
3368
  if (rel === void 0) return;
@@ -3573,8 +3616,9 @@ function changeRows(input, sheetName, at, count, style, limits) {
3573
3616
  if (out.text !== text) edit.write(s.part, out.text);
3574
3617
  rewriteTables(edit, pkg, s.part, s, c, target);
3575
3618
  if (target) {
3576
- rewriteNotes(edit, pkg, s.part, text, s, c);
3577
- rewriteDrawing(edit, pkg, s.part, text, s, c);
3619
+ const ids = drawingIds(text, s.part, edit.limits);
3620
+ rewriteNotes(edit, pkg, s.part, ids, s, c);
3621
+ rewriteDrawing(edit, pkg, s.part, ids, s, c);
3578
3622
  }
3579
3623
  }
3580
3624
  rewriteNames(edit, workbookPart, c);
@@ -77,7 +77,7 @@ interface ReadLimits {
77
77
  readonly maxXmlAttributes: number;
78
78
  /** Maximum length of an XML element or attribute name. Default 1 024 characters. */
79
79
  readonly maxXmlNameLength: number;
80
- /** Maximum length of one XML text node or attribute value. Default 8 Mi characters. */
80
+ /** Maximum length of one XML text node, attribute value, comment or processing instruction. Default 8 Mi characters. */
81
81
  readonly maxXmlTextLength: number;
82
82
  /** Maximum number of elements plus attributes in a part that is parsed into a tree (workbook, styles, relationships). Default 1 000 000. */
83
83
  readonly maxXmlTreeElements: number;
@@ -87,7 +87,8 @@ interface ReadLimits {
87
87
  * Maximum total length, in UTF-16 code units, of the shared strings that cells refer to, each
88
88
  * reference counted again. Protects against a small file that points thousands of cells at
89
89
  * one huge shared string (a few MB that become many GB once values are copied, e.g. to CSV).
90
- * Default 512 Mi.
90
+ * The same limit applies, counted separately, to shared formulas: the length of the formula of
91
+ * each cell that shares one, as moved to that cell. Default 512 Mi.
91
92
  */
92
93
  readonly maxSharedStringChars: number;
93
94
  }
@@ -122,7 +123,7 @@ declare const ERROR_CODES: readonly ["XLV_E_INVALID_INPUT", "XLV_E_INVALID_OPTIO
122
123
  * console.log(WARNING_CODES.includes('XLV_W_MACROS_PRESENT')); // true
123
124
  * ```
124
125
  */
125
- declare const WARNING_CODES: readonly ["XLV_W_DIMENSION_MISMATCH", "XLV_W_SHARED_FORMULA_NOT_EXPANDED", "XLV_W_FORMULA_NO_CACHED_VALUE", "XLV_W_VALUES_IN_MERGED_CELLS", "XLV_W_VERY_HIDDEN_SHEET", "XLV_W_MACROS_PRESENT", "XLV_W_EXTERNAL_LINKS", "XLV_W_DUPLICATE_CELL", "XLV_W_INVALID_VALUE", "XLV_W_SHEET_PART_MISSING", "XLV_W_PART_MISSING", "XLV_W_FORMULAS_NOT_RECALCULATED", "XLV_W_INVALID_CELL_REF", "XLV_W_UNUSUAL_HYPERLINK", "XLV_W_THREADED_COMMENTS", "XLV_W_DRAWING_CONTENT_NOT_READ", "XLV_W_MERGED_CELL_WRITE"];
126
+ declare const WARNING_CODES: readonly ["XLV_W_DIMENSION_MISMATCH", "XLV_W_SHARED_FORMULA_NOT_EXPANDED", "XLV_W_FORMULA_NO_CACHED_VALUE", "XLV_W_VALUES_IN_MERGED_CELLS", "XLV_W_VERY_HIDDEN_SHEET", "XLV_W_MACROS_PRESENT", "XLV_W_EXTERNAL_LINKS", "XLV_W_DUPLICATE_CELL", "XLV_W_INVALID_VALUE", "XLV_W_SHEET_PART_MISSING", "XLV_W_SHEET_PART_SHARED", "XLV_W_PART_MISSING", "XLV_W_FORMULAS_NOT_RECALCULATED", "XLV_W_INVALID_CELL_REF", "XLV_W_UNUSUAL_HYPERLINK", "XLV_W_THREADED_COMMENTS", "XLV_W_DRAWING_CONTENT_NOT_READ", "XLV_W_MERGED_CELL_WRITE", "XLV_W_LIMIT_REACHED"];
126
127
  /**
127
128
  * A stable error code, see {@link ERROR_CODES}.
128
129
  *
@@ -2594,7 +2595,7 @@ interface Sheet {
2594
2595
  readonly sheetId: number | null;
2595
2596
  readonly kind: SheetKind;
2596
2597
  readonly state: SheetState;
2597
- /** Part name of the sheet, for example `/xl/worksheets/sheet1.xml`; `null` if it is missing. */
2598
+ /** Part name of the sheet, for example `/xl/worksheets/sheet1.xml`; `null` if it is missing or is the part of an earlier sheet (`XLV_W_SHEET_PART_SHARED`). */
2598
2599
  readonly part: string | null;
2599
2600
  /** The used range declared in the file (`dimension` element), or `null`. */
2600
2601
  readonly dimension: string | null;
@@ -4675,7 +4676,9 @@ declare const STREAM_LIMIT_DEFAULTS: Partial<ReadLimits>;
4675
4676
  */
4676
4677
  declare function openWorkbookStream(source: Uint8Array | ArrayBuffer | Blob, options?: OpenWorkbookStreamOptions): Promise<WorkbookStream>;
4677
4678
  /**
4678
- * Options for {@link readTableStream}.
4679
+ * Options for {@link readTableStream}. As with `readTable`, at most `max(limits.maxCells, 16 ×
4680
+ * cells read)` row properties are produced (`limits.maxCells` defaults to 5 000 000 here,
4681
+ * although it does not limit the cells streamed).
4679
4682
  *
4680
4683
  * @example
4681
4684
  * ```ts
@@ -1,4 +1,4 @@
1
- import { $t as resolveImage, A as limitExceeded, C as Bytes, D as TAIL_BYTES, Dt as normalizeTable, E as NotLoaded, En as XlvError, Et as TABLE_CONTENT_TYPE, F as validateLocalHeaders, Ft as normalizeStyle, Ht as printXml, I as WarningCollector, Jt as IMAGE_CONTENT_TYPES, K as SheetRels, L as sheetNotFound, N as readCentralDirectory, O as checkSource$1, Ot as checkDefinedName, P as unsupported, Q as createSharedStrings, Qt as metricsOf, R as createWorksheetParser, Rt as DEFAULT_READ_LIMITS, S as workbookPartsToLoad, St as openPackage, T as HEAD_BYTES, Ut as sheetProtectionXml, Vt as printNames, X as cellXml, Xt as drawingParts, Y as buildStyles, Yt as MediaParts, Z as contentTypesXml, _ as isoDate, _n as createXmlByteStreamParser, _t as FormulaGroups, a as DATE_FORMAT, an as columnLetters, ct as sheetHead, d as convertCellInput, dn as CORE_PROPERTIES_PART, dt as sheetTail, f as createWorkbook, fn as appPropertiesXml, gt as workbookXml, h as detectHeader, ht as workbookRelsXml, i as readOptions, in as MAX_ROWS, it as noteParts, j as locateCentralDirectory, k as corrupt, kt as checkUniqueName, lt as sheetRelsPartName, n as oneOfOption, nt as hyperlinksXml, o as DATE_TIME_FORMAT, on as formatCellRef, ot as rootRelsXml, p as sortedNotes, pn as corePropertiesXml, pt as tableParts, qt as DRAWING_CONTENT_TYPE, rn as MAX_COLUMNS, rt as nameTitles, st as rowStart, tn as classifyRelationship, u as checkBookViews, un as APP_PROPERTIES_PART, ut as sheetRulesXml, v as analyzeWorkbook, w as CORRUPT_HINT, y as readNotes, yt as crc32, zt as resolveLimits } from "./options-CwSp2pA4.js";
1
+ import { $t as metricsOf, A as limitExceeded, Bt as resolveLimits, C as Bytes, D as TAIL_BYTES, Dt as normalizeTable, E as NotLoaded, Et as TABLE_CONTENT_TYPE, F as validateLocalHeaders, Ft as normalizeStyle, Ht as printNames, I as WarningCollector, Jt as DRAWING_CONTENT_TYPE, K as SheetRels, L as sheetNotFound, N as readCentralDirectory, O as checkSource$1, On as XlvError, Ot as checkDefinedName, P as unsupported, Q as createSharedStrings, R as createWorksheetParser, Rt as DEFAULT_READ_LIMITS, S as workbookPartsToLoad, St as openPackage, T as HEAD_BYTES, Ut as printXml, Wt as sheetProtectionXml, X as cellXml, Xt as MediaParts, Y as buildStyles, Yt as IMAGE_CONTENT_TYPES, Z as contentTypesXml, Zt as drawingParts, _ as isoDate, _t as FormulaGroups, a as DATE_FORMAT, an as MAX_COLUMNS, cn as formatCellRef, ct as sheetHead, d as convertCellInput, dt as sheetTail, en as resolveImage, f as createWorkbook, fn as APP_PROPERTIES_PART, gt as workbookXml, h as detectHeader, hn as corePropertiesXml, ht as workbookRelsXml, i as readOptions, it as noteParts, j as locateCentralDirectory, k as corrupt, kt as checkUniqueName, lt as sheetRelsPartName, mn as appPropertiesXml, n as oneOfOption, nt as hyperlinksXml, o as DATE_TIME_FORMAT, on as MAX_ROWS, ot as rootRelsXml, p as sortedNotes, pn as CORE_PROPERTIES_PART, pt as tableParts, rn as classifyRelationship, rt as nameTitles, sn as columnLetters, st as rowStart, u as checkBookViews, ut as sheetRulesXml, v as analyzeWorkbook, w as CORRUPT_HINT, wt as partKey, y as readNotes, yn as createXmlByteStreamParser, yt as crc32, zt as checkOutputPositions } from "./options-Ba6r4zP1.js";
2
2
  import { Deflate, Inflate } from "fflate";
3
3
  // src/zip/stream.ts
4
4
  /**
@@ -78,7 +78,7 @@ async function openZipSource(source, limits) {
78
78
  const dir = await withLoading(b, src, () => locateCentralDirectory(b, limits));
79
79
  if (!b.has(dir.centralOffset, dir.centralLimit) && dir.centralLimit > dir.centralOffset) b.add(dir.centralOffset, await src.read(dir.centralOffset, dir.centralLimit));
80
80
  const entries = await withLoading(b, src, () => readCentralDirectory(b, dir.centralOffset, dir.centralLimit, dir.entryCount));
81
- for (const entry of entries) {
81
+ for (const entry of [...entries].sort((x, y) => x.headerOffset - y.headerOffset)) {
82
82
  const at = entry.headerOffset;
83
83
  if (at + 30 > dir.centralOffset || b.has(at, at + 30)) continue;
84
84
  const fixed = await src.read(at, at + 30);
@@ -503,9 +503,9 @@ async function openWorkbookStream(source, options) {
503
503
  if (entry.kind !== "worksheet" || entry.part === null) return [];
504
504
  const cached = noteCache.get(entry.part);
505
505
  if (cached !== void 0) return cached;
506
- const parts = pkg.relationships(entry.part).filter((r) => !r.external && ["comments", "vmlDrawing"].includes(classifyRelationship(r.type)?.name ?? "") && pkg.hasPart(r.target));
506
+ const parts = pkg.relationships(entry.part).filter((r) => !r.external && ["comments", "vmlDrawing"].includes(classifyRelationship(r.type)?.name ?? "") && pkg.hasPart(r.target) && !entry.foreignParts.has(partKey(r.target)));
507
507
  if (parts.length > 0) await preloaded.load(parts.map((r) => zipName(r.target)));
508
- const notes = Object.freeze(readNotes(pkg, entry.part, entry.name, limits, warnings));
508
+ const notes = Object.freeze(readNotes(pkg, entry.part, entry.name, limits, warnings, entry.foreignParts));
509
509
  noteCache.set(entry.part, notes);
510
510
  return notes;
511
511
  }
@@ -538,7 +538,8 @@ async function openWorkbookStream(source, options) {
538
538
  conformance: info.conformance,
539
539
  budget: {
540
540
  remaining: Infinity,
541
- sharedText: limits.maxSharedStringChars
541
+ sharedText: limits.maxSharedStringChars,
542
+ formulaText: limits.maxSharedStringChars
542
543
  },
543
544
  formatCache
544
545
  }, (number, cells, rowInfo, styledCells) => ready.push({
@@ -638,6 +639,7 @@ async function* readTableStream(source, options) {
638
639
  }
639
640
  const limits = o.get("limits");
640
641
  const book = await openWorkbookStream(source, limits === void 0 ? void 0 : { limits });
642
+ const outputCells = limits?.maxCells ?? DEFAULT_READ_LIMITS.maxCells;
641
643
  const sheetName = o.get("sheet") ?? (book.sheets.find((s) => s.kind === "worksheet" && s.state === "visible") ?? book.sheets[0])?.name;
642
644
  if (sheetName === void 0) return;
643
645
  const buffered = [];
@@ -659,17 +661,25 @@ async function* readTableStream(source, options) {
659
661
  headerRow = header;
660
662
  }
661
663
  const source = headerRow === null ? nonEmpty.flatMap((r) => r.cells) : nonEmpty.find((r) => r.number === headerRow)?.cells ?? [];
662
- const minCol = Math.min(...source.map((c) => c.col));
663
- const maxCol = Math.max(...source.map((c) => c.col));
664
+ let minCol = Infinity;
665
+ let maxCol = -Infinity;
666
+ for (const c of source) {
667
+ if (c.col < minCol) minCol = c.col;
668
+ if (c.col > maxCol) maxCol = c.col;
669
+ }
670
+ const headerCells = new Map(headerRow === null ? [] : source.map((c) => [c.col, c]));
664
671
  const out = [];
665
672
  const used = /* @__PURE__ */ new Set();
673
+ const nextSuffix = /* @__PURE__ */ new Map();
666
674
  for (let col = minCol; col <= maxCol; col++) {
667
675
  const letter = columnLetters(col);
668
- const cell = headerRow === null ? void 0 : source.find((c) => c.col === col);
676
+ const cell = headerCells.get(col);
669
677
  const text = cell === void 0 || cell.value === null ? null : cell.value instanceof Date ? isoDate(cell.value) : String(cell.value);
670
678
  const base = text === null || text.trim() === "" ? letter : text.trim();
671
679
  let key = base;
672
- for (let n = 2; used.has(key); n++) key = `${base}_${String(n)}`;
680
+ let n = nextSuffix.get(base) ?? 2;
681
+ while (used.has(key)) key = `${base}_${String(n++)}`;
682
+ nextSuffix.set(base, n);
673
683
  used.add(key);
674
684
  out.push({
675
685
  key,
@@ -680,8 +690,13 @@ async function* readTableStream(source, options) {
680
690
  }
681
691
  return out;
682
692
  };
693
+ let positions = 0;
694
+ let cellsRead = 0;
683
695
  const emit = async (r, cols) => {
684
696
  if (headerRow !== null && r.number <= headerRow) return null;
697
+ cellsRead += r.cells.length;
698
+ positions += cols.length;
699
+ checkOutputPositions(positions, cellsRead, outputCells, "The table", sheetName);
685
700
  const byCol = new Map(r.cells.map((c) => [c.col, c]));
686
701
  if (cols.every((c) => byCol.get(c.col)?.value == null)) return null;
687
702
  const row = {};
package/docs/errors.md CHANGED
@@ -61,11 +61,13 @@ Each code links to its section, which says what to do. The CLI prints the code a
61
61
  | [`XLV_W_MACROS_PRESENT`](#xlv_w_macros_present) | The workbook contains a VBA project or Excel 4.0 macro sheets. |
62
62
  | [`XLV_W_EXTERNAL_LINKS`](#xlv_w_external_links) | The workbook links to other workbooks. |
63
63
  | [`XLV_W_DUPLICATE_CELL`](#xlv_w_duplicate_cell) | A sheet defines the same cell more than once. |
64
+ | [`XLV_W_LIMIT_REACHED`](#xlv_w_limit_reached) | Reading stopped computing something derived from the file because the work would be far out of proportion to its data. |
64
65
  | [`XLV_W_INVALID_VALUE`](#xlv_w_invalid_value) | A value does not match its declared type and is returned in a safe form, for example a numeric cell whose text is not a number (returned … |
65
66
  | [`XLV_W_UNUSUAL_HYPERLINK`](#xlv_w_unusual_hyperlink) | A sheet has hyperlinks whose external target uses a scheme other than `http`, `https` or `mailto`, for example `file:`, `javascript:` or … |
66
67
  | [`XLV_W_DRAWING_CONTENT_NOT_READ`](#xlv_w_drawing_content_not_read) | The drawing of a sheet holds objects other than pictures: charts, shapes, connectors, group shapes, content parts or alternate content. |
67
68
  | [`XLV_W_THREADED_COMMENTS`](#xlv_w_threaded_comments) | A sheet has threaded comments, the conversations of current Excel versions ([MS-XLSX] §2.3.7). |
68
69
  | [`XLV_W_SHEET_PART_MISSING`](#xlv_w_sheet_part_missing) | A sheet listed in the workbook has no part (the relationship or the part itself is missing). |
70
+ | [`XLV_W_SHEET_PART_SHARED`](#xlv_w_sheet_part_shared) | Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML or table part. |
69
71
  | [`XLV_W_PART_MISSING`](#xlv_w_part_missing) | The workbook points to a styles or shared strings part that does not exist. |
70
72
  | [`XLV_W_FORMULAS_NOT_RECALCULATED`](#xlv_w_formulas_not_recalculated) | Reported by `editCells` and `appendRows` when the workbook contains formulas. |
71
73
  | [`XLV_W_INVALID_CELL_REF`](#xlv_w_invalid_cell_ref) | A worksheet contains a row whose number is not a whole number from 1 to 1048576, a cell whose `r` reference is not a valid A1 reference … |
@@ -89,6 +91,12 @@ An argument of a method of `@xlvane/compat` is not valid, in a case where excelj
89
91
  ### XLV_E_LIMIT_EXCEEDED
90
92
  The file exceeds one of the resource limits (`ReadLimits`): input size, number of ZIP entries, decompressed size of one entry or of all entries, compression ratio, XML nesting depth, attribute count, name or text length, number of elements in a tree-parsed part, or number of cells. `details.limit` names the limit and `details.value` its current value. Sizes are measured on the bytes actually produced by decompression, so a file cannot avoid a limit by declaring false sizes.
91
93
 
94
+ Some limits also bound work derived from a small file (1.1.1, D-H01):
95
+
96
+ - `maxCells` (`details.limit` `maxCells`, `details.value` the budget): `toCSV`, `readTable`, `toJSON`, `readTableStream`, `xlvane read` and `xlvane convert` write at most `max(maxCells, 16 × cells read)` positions (rows × columns of the output), so a few cells far apart cannot make them produce a value for every position between; `@xlvane/compat`'s `xlsx.load` creates at most as many cells for merged ranges, tables and column definitions, and looks up at most 64 Mi rows (or 16 per cell read) for row settings up to the sheets' dimensions. Pass a smaller `range`, or raise `maxCells` for a trusted file.
97
+ - `maxXmlTextLength`: also the length of an XML comment or processing instruction.
98
+ - `maxSharedStringChars`: also the text of shared formulas, counted apart from shared strings: the length of each sharing cell's formula, as moved to that cell.
99
+
92
100
  `fromCSV` (and `xlvane convert`/`create` with CSV input) throws it for text with more fields than `maxCells` (default 5 000 000, `details.limit` `maxCells`), more than 16 384 fields on a line or more than 1 048 576 lines.
93
101
 
94
102
  `NewSheet.validate` throws it when a sheet would get more than 65534 data validations ([MS-OI29500] §2.1.614 g; `details.limit` `dataValidations`). When reading, the conditional formatting and data validations of one sheet count against `maxXmlTreeElements`.
@@ -162,6 +170,7 @@ A part exists but has the wrong structure: for example the workbook part's root
162
170
  | `target-mode` | `TargetMode` is neither `Internal` nor `External` |
163
171
  | `internal-target` | an internal relationship target cannot be resolved to a part name |
164
172
  | `equivalent-part-names` | two ZIP entries map to equivalent part names (e.g. a raw UTF-8 name and its percent-encoded form) |
173
+
165
174
  **Fix:** check that the file is an Excel workbook (not a Word or PowerPoint document) and re-save it in a spreadsheet application.
166
175
 
167
176
  ### XLV_E_INVALID_CELL_REF
@@ -289,6 +298,10 @@ Some merged ranges have values in cells other than the top-left one. Spreadsheet
289
298
  `editCells` or `appendRows` wrote a value into a cell of a merged range other than its top-left cell. The value is stored, but spreadsheet applications show only the top-left cell of a merged range, so the value is not visible. `cells` lists the cells written (up to ten); the message names the first one and its merged range.
290
299
  **Fix:** write the value into the top-left cell of the range (named in the message), or unmerge the range first.
291
300
 
301
+ ### XLV_W_LIMIT_REACHED
302
+ Reading stopped computing something derived from the file because the work would be far out of proportion to its data, and the file is read without it. Today: hyperlinks whose ranges cover far more positions than the sheet has cells (more than 16 visits per cell plus 1 000 000); `sheet.hyperlinks` lists every link, but `cell.hyperlink` is not set for the cells of the remaining links (D-H01).
303
+ **Fix:** nothing, if the file comes from an unknown source: this protects the reader. Look up a cell's link in `sheet.hyperlinks` by its range.
304
+
292
305
  ### XLV_W_VERY_HIDDEN_SHEET
293
306
  A sheet has the state `veryHidden`: spreadsheet applications offer no menu command to unhide it, so users usually do not know it exists.
294
307
  **Fix:** nothing, if the sheet is expected; `inspect` lists it with its state, and `workbook.sheets` includes it.
@@ -325,6 +338,10 @@ A sheet has threaded comments, the conversations of current Excel versions ([MS-
325
338
  A sheet listed in the workbook has no part (the relationship or the part itself is missing). The sheet is returned with `part: null` and no cells.
326
339
  **Fix:** re-save the file in the application that made it; the sheet's cells are not in the file.
327
340
 
341
+ ### XLV_W_SHEET_PART_SHARED
342
+ Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML or table part (part names compared without regard to case, as OPC requires). Spreadsheet applications write one such part per sheet; reading a shared part again for every sheet would decompress and parse it many times, outside the limit on decompressed bytes. The part is read for the first of these sheets only: for a worksheet part, the others are returned with `part: null` and no cells; for a comments, VML or table part, the others are read without its notes or table. A sheet that relates to one table part twice gets that table once. The location names the sheet and the part.
343
+ **Fix:** re-save the file in the application that made it, and check whether the sheets returned without cells should hold data.
344
+
328
345
  ### XLV_W_PART_MISSING
329
346
  The workbook points to a styles or shared strings part that does not exist. Reading continues without it: without styles, date-formatted numbers are returned as numbers; without shared strings, shared-string cells are treated as empty.
330
347
  **Fix:** re-save the file in the application that made it; check number formats and text cells of the result.
@@ -38,7 +38,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
38
38
  | `xlsx.write(stream)` | supported | | `lib/xlsx/xlsx.js` L643 |
39
39
  | `xlsx.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/xlsx/xlsx.js` L238 |
40
40
  | `csv.read(stream, options)`, `csv.readFile(path, options)` | supported | Adds a worksheet, with exceljs's options (`sheetName`, `dateFormats`, `map`, `parserOptions`: `delimiter`, `quote`, `escape`, `trim`/`ltrim`/`rtrim`, `ignoreEmpty`, `comment`, `skipLines`, `skipRows`, `maxRows`, `headers`, `renameHeaders`, `encoding`) and its default conversion of fields (X4-23), on xlvane's own parser (no fast-csv). Accepts a Node.js readable stream, a Web `ReadableStream` or any (async) iterable of strings or bytes. Differences: text of white space only stays text (exceljs reads `' '` as 0), `-Infinity` stays text (it cannot be stored), a leading byte order mark is dropped (exceljs keeps it in the first field), `headers: true` skips the header line (exceljs throws `data.map is not a function`), a missing file or a malformed line throws `XLV_E_FILE_READ` / `XLV_E_CSV_MALFORMED`. `readFile` needs `node:fs` (Node.js, Bun, Deno). Dates are parsed as dayjs 1.11 customParseFormat parses them in strict mode (D-E06): letters that are no token, such as an unbracketed `T` or `dddd`, are skipped, and each token is searched in the rest of the text. An unclosed quote is reported on the line where it opens; a `quote` or `escape` longer than one character throws `XLV_E_INVALID_OPTION`. | `lib/csv/csv.js` L32–98 |
41
- | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`) and fast-csv's quoting (X4-24); the cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR or LF is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`. | `lib/csv/csv.js` L109–186 |
41
+ | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`) and fast-csv's quoting (X4-24); the cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10, CSV-12; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`. | `lib/csv/csv.js` L109–186 |
42
42
  | `csv.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/csv/csv.js` L101 |
43
43
  | `definedNames` (`add`, `addEx`, `remove`, `removeEx`, `removeAllNames`, `forEach`, `getNames`, `getNamesEx`, `getRanges`, `getMatrix`, `model`) | supported | As in exceljs, a name is a set of cells, written as rectangles with absolute references and no sheet scope; on reading, names that are not cell ranges (constants, formulas, `#REF!`) and the sheet scope (`localSheetId`) are dropped. Built-in `_xlnm.` names are not listed: print areas and titles are read into `pageSetup`, as in exceljs, and xlvane writes the autofilter name itself. Names exceljs accepts but Excel does not (for example `A1` or names starting with `_xl`) throw `XLV_E_INVALID_DEFINED_NAME` when the workbook is written. | `lib/doc/defined-names.js`; `lib/xlsx/xform/book/defined-name-xform.js`; `lib/xlsx/xform/book/workbook-xform.js` L187 |
44
44
  | `addImage(image)`, `getImage(id)` | supported | PNG, JPEG and GIF given as `buffer`, `base64` (a data URL is accepted, as in exceljs) or `filename`; the format is recognised from the bytes, `extension` is kept but not used (exceljs writes `image/jpg` for `extension: 'jpg'`). As in exceljs, a `filename` is read when the workbook is written (`xlsx.writeBuffer`/`writeFile`/`write` and the streaming `WorkbookWriter`), so a missing file fails there; outside Node.js, Bun and Deno it gives `XLV_E_NOT_SUPPORTED`. Other formats throw `XLV_E_INVALID_IMAGE`. Read back, each image part is one media entry `{ type, name, extension, buffer }`, `buffer` a `Uint8Array`. Identical images are stored once, with one relationship per drawing (exceljs writes one per use). | `lib/doc/workbook.js` L142–151; `lib/xlsx/xlsx.js` L421–442 (L426–429: `filename` read at write) |
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # xlvane 1.1.0: documentation in one file
1
+ # xlvane 1.1.1: documentation in one file
2
2
 
3
3
  This file joins the README of the xlvane package, the Agent Skill and the documentation in docs/. It is generated by scripts/package-docs.mjs; the separate files are in the same package.
4
4
 
@@ -391,10 +391,10 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
391
391
  | `maxXmlDepth` | 128 | deeply nested XML |
392
392
  | `maxXmlAttributes` | 256 | elements with huge numbers of attributes |
393
393
  | `maxXmlNameLength` | 1 024 | huge element or attribute names |
394
- | `maxXmlTextLength` | 8 Mi characters | huge text nodes or attribute values |
394
+ | `maxXmlTextLength` | 8 Mi characters | huge text nodes, attribute values, comments or processing instructions |
395
395
  | `maxXmlTreeElements` | 1 000 000 | huge workbook, styles or relationship parts (elements plus attributes) |
396
- | `maxCells` | 5 000 000 | workbooks too large to hold in memory (measured: about 180 bytes per numeric cell, Node.js 26) |
397
- | `maxSharedStringChars` | 512 Mi characters | many cells pointing at one huge shared string (a few MB that become many GB when values are copied) |
396
+ | `maxCells` | 5 000 000 | workbooks too large to hold in memory (measured: about 180 bytes per numeric cell, Node.js 26); also bounds the output of `toCSV`, `readTable`, `toJSON` and `readTableStream` to `max(maxCells, 16 × cells read)` positions |
397
+ | `maxSharedStringChars` | 512 Mi characters | many cells pointing at one huge shared string (a few MB that become many GB when values are copied); counted separately, the same for cells sharing one huge formula |
398
398
 
399
399
  ## Package size
400
400
 
@@ -409,6 +409,7 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
409
409
 
410
410
  ## Links
411
411
 
412
+ - Website with the guide and an in-browser demo: [xlvane.github.io](https://xlvane.github.io)
412
413
  - Changes in each version: [CHANGELOG.md](https://github.com/DevXord/xlvane/blob/main/CHANGELOG.md)
413
414
  - Bugs and questions: [GitHub issues](https://github.com/DevXord/xlvane/issues)
414
415
  - Reporting a vulnerability: [SECURITY.md](https://github.com/DevXord/xlvane/blob/main/SECURITY.md)
@@ -693,11 +694,13 @@ Each code links to its section, which says what to do. The CLI prints the code a
693
694
  | [`XLV_W_MACROS_PRESENT`](#xlv_w_macros_present) | The workbook contains a VBA project or Excel 4.0 macro sheets. |
694
695
  | [`XLV_W_EXTERNAL_LINKS`](#xlv_w_external_links) | The workbook links to other workbooks. |
695
696
  | [`XLV_W_DUPLICATE_CELL`](#xlv_w_duplicate_cell) | A sheet defines the same cell more than once. |
697
+ | [`XLV_W_LIMIT_REACHED`](#xlv_w_limit_reached) | Reading stopped computing something derived from the file because the work would be far out of proportion to its data. |
696
698
  | [`XLV_W_INVALID_VALUE`](#xlv_w_invalid_value) | A value does not match its declared type and is returned in a safe form, for example a numeric cell whose text is not a number (returned … |
697
699
  | [`XLV_W_UNUSUAL_HYPERLINK`](#xlv_w_unusual_hyperlink) | A sheet has hyperlinks whose external target uses a scheme other than `http`, `https` or `mailto`, for example `file:`, `javascript:` or … |
698
700
  | [`XLV_W_DRAWING_CONTENT_NOT_READ`](#xlv_w_drawing_content_not_read) | The drawing of a sheet holds objects other than pictures: charts, shapes, connectors, group shapes, content parts or alternate content. |
699
701
  | [`XLV_W_THREADED_COMMENTS`](#xlv_w_threaded_comments) | A sheet has threaded comments, the conversations of current Excel versions ([MS-XLSX] §2.3.7). |
700
702
  | [`XLV_W_SHEET_PART_MISSING`](#xlv_w_sheet_part_missing) | A sheet listed in the workbook has no part (the relationship or the part itself is missing). |
703
+ | [`XLV_W_SHEET_PART_SHARED`](#xlv_w_sheet_part_shared) | Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML or table part. |
701
704
  | [`XLV_W_PART_MISSING`](#xlv_w_part_missing) | The workbook points to a styles or shared strings part that does not exist. |
702
705
  | [`XLV_W_FORMULAS_NOT_RECALCULATED`](#xlv_w_formulas_not_recalculated) | Reported by `editCells` and `appendRows` when the workbook contains formulas. |
703
706
  | [`XLV_W_INVALID_CELL_REF`](#xlv_w_invalid_cell_ref) | A worksheet contains a row whose number is not a whole number from 1 to 1048576, a cell whose `r` reference is not a valid A1 reference … |
@@ -721,6 +724,12 @@ An argument of a method of `@xlvane/compat` is not valid, in a case where excelj
721
724
  ### XLV_E_LIMIT_EXCEEDED
722
725
  The file exceeds one of the resource limits (`ReadLimits`): input size, number of ZIP entries, decompressed size of one entry or of all entries, compression ratio, XML nesting depth, attribute count, name or text length, number of elements in a tree-parsed part, or number of cells. `details.limit` names the limit and `details.value` its current value. Sizes are measured on the bytes actually produced by decompression, so a file cannot avoid a limit by declaring false sizes.
723
726
 
727
+ Some limits also bound work derived from a small file (1.1.1, D-H01):
728
+
729
+ - `maxCells` (`details.limit` `maxCells`, `details.value` the budget): `toCSV`, `readTable`, `toJSON`, `readTableStream`, `xlvane read` and `xlvane convert` write at most `max(maxCells, 16 × cells read)` positions (rows × columns of the output), so a few cells far apart cannot make them produce a value for every position between; `@xlvane/compat`'s `xlsx.load` creates at most as many cells for merged ranges, tables and column definitions, and looks up at most 64 Mi rows (or 16 per cell read) for row settings up to the sheets' dimensions. Pass a smaller `range`, or raise `maxCells` for a trusted file.
730
+ - `maxXmlTextLength`: also the length of an XML comment or processing instruction.
731
+ - `maxSharedStringChars`: also the text of shared formulas, counted apart from shared strings: the length of each sharing cell's formula, as moved to that cell.
732
+
724
733
  `fromCSV` (and `xlvane convert`/`create` with CSV input) throws it for text with more fields than `maxCells` (default 5 000 000, `details.limit` `maxCells`), more than 16 384 fields on a line or more than 1 048 576 lines.
725
734
 
726
735
  `NewSheet.validate` throws it when a sheet would get more than 65534 data validations ([MS-OI29500] §2.1.614 g; `details.limit` `dataValidations`). When reading, the conditional formatting and data validations of one sheet count against `maxXmlTreeElements`.
@@ -794,6 +803,7 @@ A part exists but has the wrong structure: for example the workbook part's root
794
803
  | `target-mode` | `TargetMode` is neither `Internal` nor `External` |
795
804
  | `internal-target` | an internal relationship target cannot be resolved to a part name |
796
805
  | `equivalent-part-names` | two ZIP entries map to equivalent part names (e.g. a raw UTF-8 name and its percent-encoded form) |
806
+
797
807
  **Fix:** check that the file is an Excel workbook (not a Word or PowerPoint document) and re-save it in a spreadsheet application.
798
808
 
799
809
  ### XLV_E_INVALID_CELL_REF
@@ -921,6 +931,10 @@ Some merged ranges have values in cells other than the top-left one. Spreadsheet
921
931
  `editCells` or `appendRows` wrote a value into a cell of a merged range other than its top-left cell. The value is stored, but spreadsheet applications show only the top-left cell of a merged range, so the value is not visible. `cells` lists the cells written (up to ten); the message names the first one and its merged range.
922
932
  **Fix:** write the value into the top-left cell of the range (named in the message), or unmerge the range first.
923
933
 
934
+ ### XLV_W_LIMIT_REACHED
935
+ Reading stopped computing something derived from the file because the work would be far out of proportion to its data, and the file is read without it. Today: hyperlinks whose ranges cover far more positions than the sheet has cells (more than 16 visits per cell plus 1 000 000); `sheet.hyperlinks` lists every link, but `cell.hyperlink` is not set for the cells of the remaining links (D-H01).
936
+ **Fix:** nothing, if the file comes from an unknown source: this protects the reader. Look up a cell's link in `sheet.hyperlinks` by its range.
937
+
924
938
  ### XLV_W_VERY_HIDDEN_SHEET
925
939
  A sheet has the state `veryHidden`: spreadsheet applications offer no menu command to unhide it, so users usually do not know it exists.
926
940
  **Fix:** nothing, if the sheet is expected; `inspect` lists it with its state, and `workbook.sheets` includes it.
@@ -957,6 +971,10 @@ A sheet has threaded comments, the conversations of current Excel versions ([MS-
957
971
  A sheet listed in the workbook has no part (the relationship or the part itself is missing). The sheet is returned with `part: null` and no cells.
958
972
  **Fix:** re-save the file in the application that made it; the sheet's cells are not in the file.
959
973
 
974
+ ### XLV_W_SHEET_PART_SHARED
975
+ Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML or table part (part names compared without regard to case, as OPC requires). Spreadsheet applications write one such part per sheet; reading a shared part again for every sheet would decompress and parse it many times, outside the limit on decompressed bytes. The part is read for the first of these sheets only: for a worksheet part, the others are returned with `part: null` and no cells; for a comments, VML or table part, the others are read without its notes or table. A sheet that relates to one table part twice gets that table once. The location names the sheet and the part.
976
+ **Fix:** re-save the file in the application that made it, and check whether the sheets returned without cells should hold data.
977
+
960
978
  ### XLV_W_PART_MISSING
961
979
  The workbook points to a styles or shared strings part that does not exist. Reading continues without it: without styles, date-formatted numbers are returned as numbers; without shared strings, shared-string cells are treated as empty.
962
980
  **Fix:** re-save the file in the application that made it; check number formats and text cells of the result.
@@ -1243,7 +1261,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1243
1261
  | `xlsx.write(stream)` | supported | | `lib/xlsx/xlsx.js` L643 |
1244
1262
  | `xlsx.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/xlsx/xlsx.js` L238 |
1245
1263
  | `csv.read(stream, options)`, `csv.readFile(path, options)` | supported | Adds a worksheet, with exceljs's options (`sheetName`, `dateFormats`, `map`, `parserOptions`: `delimiter`, `quote`, `escape`, `trim`/`ltrim`/`rtrim`, `ignoreEmpty`, `comment`, `skipLines`, `skipRows`, `maxRows`, `headers`, `renameHeaders`, `encoding`) and its default conversion of fields (X4-23), on xlvane's own parser (no fast-csv). Accepts a Node.js readable stream, a Web `ReadableStream` or any (async) iterable of strings or bytes. Differences: text of white space only stays text (exceljs reads `' '` as 0), `-Infinity` stays text (it cannot be stored), a leading byte order mark is dropped (exceljs keeps it in the first field), `headers: true` skips the header line (exceljs throws `data.map is not a function`), a missing file or a malformed line throws `XLV_E_FILE_READ` / `XLV_E_CSV_MALFORMED`. `readFile` needs `node:fs` (Node.js, Bun, Deno). Dates are parsed as dayjs 1.11 customParseFormat parses them in strict mode (D-E06): letters that are no token, such as an unbracketed `T` or `dddd`, are skipped, and each token is searched in the rest of the text. An unclosed quote is reported on the line where it opens; a `quote` or `escape` longer than one character throws `XLV_E_INVALID_OPTION`. | `lib/csv/csv.js` L32–98 |
1246
- | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`) and fast-csv's quoting (X4-24); the cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR or LF is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`. | `lib/csv/csv.js` L109–186 |
1264
+ | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`) and fast-csv's quoting (X4-24); the cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10, CSV-12; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`. | `lib/csv/csv.js` L109–186 |
1247
1265
  | `csv.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/csv/csv.js` L101 |
1248
1266
  | `definedNames` (`add`, `addEx`, `remove`, `removeEx`, `removeAllNames`, `forEach`, `getNames`, `getNamesEx`, `getRanges`, `getMatrix`, `model`) | supported | As in exceljs, a name is a set of cells, written as rectangles with absolute references and no sheet scope; on reading, names that are not cell ranges (constants, formulas, `#REF!`) and the sheet scope (`localSheetId`) are dropped. Built-in `_xlnm.` names are not listed: print areas and titles are read into `pageSetup`, as in exceljs, and xlvane writes the autofilter name itself. Names exceljs accepts but Excel does not (for example `A1` or names starting with `_xl`) throw `XLV_E_INVALID_DEFINED_NAME` when the workbook is written. | `lib/doc/defined-names.js`; `lib/xlsx/xform/book/defined-name-xform.js`; `lib/xlsx/xform/book/workbook-xform.js` L187 |
1249
1267
  | `addImage(image)`, `getImage(id)` | supported | PNG, JPEG and GIF given as `buffer`, `base64` (a data URL is accepted, as in exceljs) or `filename`; the format is recognised from the bytes, `extension` is kept but not used (exceljs writes `image/jpg` for `extension: 'jpg'`). As in exceljs, a `filename` is read when the workbook is written (`xlsx.writeBuffer`/`writeFile`/`write` and the streaming `WorkbookWriter`), so a missing file fails there; outside Node.js, Bun and Deno it gives `XLV_E_NOT_SUPPORTED`. Other formats throw `XLV_E_INVALID_IMAGE`. Read back, each image part is one media entry `{ type, name, extension, buffer }`, `buffer` a `Uint8Array`. Identical images are stored once, with one relationship per drawing (exceljs writes one per use). | `lib/doc/workbook.js` L142–151; `lib/xlsx/xlsx.js` L421–442 (L426–429: `filename` read at write) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlvane",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Read, inspect and write XLSX files; input is treated as untrusted. ESM only, one runtime dependency (fflate).",
5
5
  "keywords": [
6
6
  "xlsx",