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.
- package/README.md +4 -3
- package/dist/cli.js +147 -43
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/{node-files-B0iqSJIJ.js → node-files-CsQVpKgP.js} +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{options-CwSp2pA4.js → options-Ba6r4zP1.js} +313 -74
- package/dist/{rows-DKwlr0rg.js → rows-Biu_ddwz.js} +59 -15
- package/dist/{stream-write-GJXORYQ2.d.ts → stream-write-CaGFWPhB.d.ts} +8 -5
- package/dist/{stream-write-BKppyMz0.js → stream-write-CtWyTd44.js} +24 -9
- package/docs/errors.md +17 -0
- package/docs/exceljs-compat.md +1 -1
- package/llms-full.txt +23 -5
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as fillXml, At as EMPTY_STYLES, B as tokenize, Bt as
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 &&
|
|
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
|
-
|
|
638
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
3322
|
-
const id =
|
|
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
|
-
|
|
3577
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
663
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
package/docs/exceljs-compat.md
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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) |
|