xlvane 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/cli.js +226 -47
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/{node-files-B0iqSJIJ.js → node-files-CDb6TyZb.js} +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{options-CwSp2pA4.js → options-Ba1iWD-Z.js} +316 -75
- package/dist/{rows-DKwlr0rg.js → rows-IWKIxHt9.js} +59 -15
- package/dist/{stream-write-BKppyMz0.js → stream-write-BGuUjlXo.js} +24 -9
- package/dist/{stream-write-GJXORYQ2.d.ts → stream-write-BVaFkgwO.d.ts} +9 -6
- package/docs/cli-json/mcp.schema.json +43 -0
- package/docs/cli-json.md +2 -1
- package/docs/errors.md +27 -0
- package/docs/exceljs-compat.md +1 -1
- package/llms-full.txt +90 -10
- package/package.json +1 -1
- package/skills/xlvane/SKILL.md +8 -3
|
@@ -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-Ba1iWD-Z.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);
|
|
@@ -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-Ba1iWD-Z.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 = {};
|
|
@@ -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
|
}
|
|
@@ -111,7 +112,7 @@ declare const DEFAULT_READ_LIMITS: Readonly<ReadLimits>;
|
|
|
111
112
|
* console.log(ERROR_CODES.includes('XLV_E_SHEET_NOT_FOUND')); // true
|
|
112
113
|
* ```
|
|
113
114
|
*/
|
|
114
|
-
declare const ERROR_CODES: readonly ["XLV_E_INVALID_INPUT", "XLV_E_INVALID_OPTION", "XLV_E_INVALID_ARGUMENT", "XLV_E_LIMIT_EXCEEDED", "XLV_E_NOT_ZIP", "XLV_E_UNSUPPORTED_CONTAINER", "XLV_E_ZIP_CORRUPT", "XLV_E_ZIP_UNSUPPORTED", "XLV_E_XML_MALFORMED", "XLV_E_XML_DTD_FORBIDDEN", "XLV_E_XML_ENTITY_FORBIDDEN", "XLV_E_XML_ENCODING", "XLV_E_PART_MISSING", "XLV_E_PART_INVALID", "XLV_E_INVALID_CELL_REF", "XLV_E_SHEET_NOT_FOUND", "XLV_E_TABLE_NOT_FOUND", "XLV_E_FILE_READ", "XLV_E_INVALID_VALUE", "XLV_E_INVALID_STYLE", "XLV_E_INVALID_SHEET_NAME", "XLV_E_DUPLICATE_SHEET_NAME", "XLV_E_INVALID_DEFINED_NAME", "XLV_E_INVALID_HYPERLINK", "XLV_E_INVALID_TABLE", "XLV_E_INVALID_IMAGE", "XLV_E_OVERLAPPING_MERGE", "XLV_E_FILE_EXISTS", "XLV_E_FILE_WRITE", "XLV_E_HEADER_NOT_FOUND", "XLV_E_SCHEMA_INVALID", "XLV_E_ASYNC_SCHEMA", "XLV_E_INVALID_ROWS", "XLV_E_JSON_INVALID", "XLV_E_CSV_MALFORMED", "XLV_E_NOT_SUPPORTED", "XLV_E_USAGE", "XLV_E_INTERNAL"];
|
|
115
|
+
declare const ERROR_CODES: readonly ["XLV_E_INVALID_INPUT", "XLV_E_INVALID_OPTION", "XLV_E_INVALID_ARGUMENT", "XLV_E_LIMIT_EXCEEDED", "XLV_E_NOT_ZIP", "XLV_E_UNSUPPORTED_CONTAINER", "XLV_E_ZIP_CORRUPT", "XLV_E_ZIP_UNSUPPORTED", "XLV_E_XML_MALFORMED", "XLV_E_XML_DTD_FORBIDDEN", "XLV_E_XML_ENTITY_FORBIDDEN", "XLV_E_XML_ENCODING", "XLV_E_PART_MISSING", "XLV_E_PART_INVALID", "XLV_E_INVALID_CELL_REF", "XLV_E_SHEET_NOT_FOUND", "XLV_E_TABLE_NOT_FOUND", "XLV_E_FILE_READ", "XLV_E_INVALID_VALUE", "XLV_E_INVALID_STYLE", "XLV_E_INVALID_SHEET_NAME", "XLV_E_DUPLICATE_SHEET_NAME", "XLV_E_INVALID_DEFINED_NAME", "XLV_E_INVALID_HYPERLINK", "XLV_E_INVALID_TABLE", "XLV_E_INVALID_IMAGE", "XLV_E_OVERLAPPING_MERGE", "XLV_E_FILE_EXISTS", "XLV_E_FILE_WRITE", "XLV_E_HEADER_NOT_FOUND", "XLV_E_SCHEMA_INVALID", "XLV_E_ASYNC_SCHEMA", "XLV_E_INVALID_ROWS", "XLV_E_JSON_INVALID", "XLV_E_CSV_MALFORMED", "XLV_E_NOT_SUPPORTED", "XLV_E_USAGE", "XLV_E_NOT_INSTALLED", "XLV_E_INTERNAL", "XLV_E_PATH_NOT_ALLOWED"];
|
|
115
116
|
/**
|
|
116
117
|
* Every warning code xlvane can report. Warnings never stop reading; they describe something
|
|
117
118
|
* in the file that the caller may want to know about. Each one is described in docs/errors.md.
|
|
@@ -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
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "mcp.schema.json",
|
|
4
|
+
"title": "xlvane mcp --json (errors only; the server itself speaks MCP)",
|
|
5
|
+
"oneOf": [
|
|
6
|
+
{
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"xlvane": {
|
|
10
|
+
"const": 1
|
|
11
|
+
},
|
|
12
|
+
"command": {
|
|
13
|
+
"const": "mcp"
|
|
14
|
+
},
|
|
15
|
+
"ok": {
|
|
16
|
+
"const": true
|
|
17
|
+
},
|
|
18
|
+
"result": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"exitCode": {
|
|
22
|
+
"type": "integer"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": [
|
|
26
|
+
"exitCode"
|
|
27
|
+
],
|
|
28
|
+
"additionalProperties": false
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [
|
|
32
|
+
"xlvane",
|
|
33
|
+
"command",
|
|
34
|
+
"ok",
|
|
35
|
+
"result"
|
|
36
|
+
],
|
|
37
|
+
"additionalProperties": false
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"$ref": "common.schema.json#/$defs/errorEnvelope"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
package/docs/cli-json.md
CHANGED
|
@@ -53,7 +53,7 @@ The error envelope is defined in [cli-json/common.schema.json](cli-json/common.s
|
|
|
53
53
|
| Code | Meaning |
|
|
54
54
|
|---|---|
|
|
55
55
|
| 0 | Success. |
|
|
56
|
-
| 1 | The command ran, and a check found something: `diff` found differences, or `validate` found problems. |
|
|
56
|
+
| 1 | The command ran, and a check found something: `diff` found differences, or `validate` found problems. `mcp` returns the exit code of the server (0, or 2 and 3 as below). |
|
|
57
57
|
| 2 | Usage error (`XLV_E_USAGE`): unknown command or option, missing or extra argument, invalid option value. |
|
|
58
58
|
| 3 | Input or file error: the file cannot be read, is not a valid workbook, the sheet does not exist, the output exists without `--force`, and so on. |
|
|
59
59
|
|
|
@@ -69,3 +69,4 @@ The error envelope is defined in [cli-json/common.schema.json](cli-json/common.s
|
|
|
69
69
|
| `diff` | [cli-json/diff.schema.json](cli-json/diff.schema.json) | The `diff()` result: `equal`, `sheetsAdded`, `sheetsRemoved`, `sheets` (cell changes with `before` and `after`), `summary`. Every change is included. |
|
|
70
70
|
| `validate` | [cli-json/validate.schema.json](cli-json/validate.schema.json) | `file`, `ok`, `sheet`, `rowsChecked`, `problems` (`kind`, `column`, `cell`, `row`, `message`), `warnings`. |
|
|
71
71
|
| `migrate` | [cli-json/migrate.schema.json](cli-json/migrate.schema.json) | `directory`, `packageJson`, `changes` (`section`, `from`, `to`), `alreadyMigrated`, `enginesNode`, `filesScanned`, `scanLimited`, `findings` (`file`, `line`, `column`, `api`, `status`), `written`. |
|
|
72
|
+
| `mcp` | [cli-json/mcp.schema.json](cli-json/mcp.schema.json) | `--json` is refused with a usage error (exit code 2), printed as this document, because stdout carries the MCP protocol. |
|
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,14 @@ 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
|
+
|
|
100
|
+
The MCP server (`@xlvane/mcp`) returns it as a tool error for a file larger than `--max-file-size` (`details.limit` `maxFileBytes`) and for a result longer than `--max-response-chars` (`details.limit` `maxResponseChars`; `read_table` instead returns the rows that fit).
|
|
101
|
+
|
|
92
102
|
`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
103
|
|
|
94
104
|
`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 +172,7 @@ A part exists but has the wrong structure: for example the workbook part's root
|
|
|
162
172
|
| `target-mode` | `TargetMode` is neither `Internal` nor `External` |
|
|
163
173
|
| `internal-target` | an internal relationship target cannot be resolved to a part name |
|
|
164
174
|
| `equivalent-part-names` | two ZIP entries map to equivalent part names (e.g. a raw UTF-8 name and its percent-encoded form) |
|
|
175
|
+
|
|
165
176
|
**Fix:** check that the file is an Excel workbook (not a Word or PowerPoint document) and re-save it in a spreadsheet application.
|
|
166
177
|
|
|
167
178
|
### XLV_E_INVALID_CELL_REF
|
|
@@ -263,10 +274,18 @@ For `insertRows`/`deleteRows`, insert or delete the rows in a spreadsheet applic
|
|
|
263
274
|
The `xlvane` command line was used incorrectly: an unknown command or option, a missing or extra argument, an option without its value, or an option value that is not allowed. `details` names the command and the option or argument; `details.suggestions` lists close matches ("Did you mean ...?"). Exit code 2.
|
|
264
275
|
**Fix:** run `xlvane help <command>` for the usage and examples of the command.
|
|
265
276
|
|
|
277
|
+
### XLV_E_NOT_INSTALLED
|
|
278
|
+
`xlvane mcp` needs the separate package `@xlvane/mcp`, which could not be loaded. `details.package` names it.
|
|
279
|
+
**Fix:** install it next to xlvane, for example `npm install @xlvane/mcp`.
|
|
280
|
+
|
|
266
281
|
### XLV_E_INTERNAL
|
|
267
282
|
The `xlvane` command line caught an error that is not an `XlvError`. This is a bug in xlvane. `details.name` is the error's name; `--verbose` prints the stack trace.
|
|
268
283
|
**Fix:** report it with the command and, if possible, the input file (see SECURITY.md for files that must stay private).
|
|
269
284
|
|
|
285
|
+
### XLV_E_PATH_NOT_ALLOWED
|
|
286
|
+
The MCP server (`@xlvane/mcp`) was asked to read or write a path outside the directories it was started with (`--root`), including paths that leave them through `..` or a symbolic link, or to write through a symbolic link. Nothing was read or written. `details.path` is the path as given and `details.roots` lists the allowed directories.
|
|
287
|
+
**Fix:** use a path inside one of the listed directories, or restart the server with another `--root`.
|
|
288
|
+
|
|
270
289
|
## Warnings
|
|
271
290
|
|
|
272
291
|
### XLV_W_DIMENSION_MISMATCH
|
|
@@ -289,6 +308,10 @@ Some merged ranges have values in cells other than the top-left one. Spreadsheet
|
|
|
289
308
|
`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
309
|
**Fix:** write the value into the top-left cell of the range (named in the message), or unmerge the range first.
|
|
291
310
|
|
|
311
|
+
### XLV_W_LIMIT_REACHED
|
|
312
|
+
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).
|
|
313
|
+
**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.
|
|
314
|
+
|
|
292
315
|
### XLV_W_VERY_HIDDEN_SHEET
|
|
293
316
|
A sheet has the state `veryHidden`: spreadsheet applications offer no menu command to unhide it, so users usually do not know it exists.
|
|
294
317
|
**Fix:** nothing, if the sheet is expected; `inspect` lists it with its state, and `workbook.sheets` includes it.
|
|
@@ -325,6 +348,10 @@ A sheet has threaded comments, the conversations of current Excel versions ([MS-
|
|
|
325
348
|
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
349
|
**Fix:** re-save the file in the application that made it; the sheet's cells are not in the file.
|
|
327
350
|
|
|
351
|
+
### XLV_W_SHEET_PART_SHARED
|
|
352
|
+
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.
|
|
353
|
+
**Fix:** re-save the file in the application that made it, and check whether the sheets returned without cells should hold data.
|
|
354
|
+
|
|
328
355
|
### XLV_W_PART_MISSING
|
|
329
356
|
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
357
|
**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) |
|