xlvane 1.2.2 → 1.3.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 +37 -6
- package/dist/cli.js +125 -45
- package/dist/index.d.ts +33 -11
- package/dist/index.js +3 -3
- package/dist/{node-files-CDb6TyZb.js → node-files-SVg17pky.js} +2 -2
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{options-Ba1iWD-Z.js → options-Cxt-LJE4.js} +8107 -6923
- package/dist/{rows-IWKIxHt9.js → rows-DltZ-hiW.js} +1116 -396
- package/dist/{stream-write-BGuUjlXo.js → stream-write-D573ewpr.js} +112 -16
- package/dist/{stream-write-BVaFkgwO.d.ts → stream-write-XnCyLdOy.d.ts} +354 -8
- package/docs/cli-json/inspect.schema.json +76 -0
- package/docs/errors.md +33 -12
- package/docs/exceljs-compat.md +32 -12
- package/docs/inspect-json.md +4 -0
- package/docs/migrate-from-exceljs.md +131 -0
- package/docs/xlsx-cves.md +88 -0
- package/llms-full.txt +422 -32
- package/package.json +1 -1
- package/skills/xlvane/SKILL.md +14 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $t as metricsOf, A as
|
|
1
|
+
import { $t as metricsOf, A as checkSource$1, B as createWorksheetParser, Bt as resolveLimits, Cn as CORE_PROPERTIES_PART, Ct as relationshipsPartName, D as HEAD_BYTES, Dt as checkUniqueName, E as CORRUPT_HINT, Et as checkDefinedName, F as readCentralDirectory, Ht as printNames, I as unsupported, Jt as DRAWING_CONTENT_TYPE, K as SheetRels, L as validateLocalHeaders, Lt as DEFAULT_READ_LIMITS, M as limitExceeded, N as locateCentralDirectory, Nn as clip, O as NotLoaded, Ot as nameScopeKey, Pt as normalizeStyle, Q as createSharedStrings, R as WarningCollector, Rt as checkKeyText, Sn as APP_PROPERTIES_PART, St as openPackage, T as Bytes, Tn as corePropertiesXml, Tt as normalizeTable, 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, _t as FormulaGroups, a as DATE_FORMAT, b as readNotes, bn as partKey, cn as chartPartXml, ct as sheetHead, d as convertCellInput, dt as sheetTail, en as resolveAnchor, f as createWorkbook, g as detectHeader, gn as formatCellRef, gt as workbookXml, hn as columnLetters, ht as workbookRelsXml, i as readOptions, in as classifyRelationship, it as noteParts, j as corrupt, k as TAIL_BYTES, kn as createXmlByteStreamParser, ln as chartSheetNames, lt as sheetRelsPartName, mn as MAX_ROWS, n as oneOfOption, nt as hyperlinksXml, o as DATE_TIME_FORMAT, on as CHART_CONTENT_TYPE, ot as rootRelsXml, p as sortedNotes, pn as MAX_COLUMNS, pt as tableParts, rt as nameTitles, sn as chartPartName, st as rowStart, tn as resolveImage, u as checkBookViews, un as resolveChartSheets, ut as sheetRulesXml, v as isoDate, w as workbookPartsToLoad, wn as appPropertiesXml, wt as TABLE_CONTENT_TYPE, xn as zipNameToPartName, y as analyzeWorkbook, yt as crc32, z as sheetNotFound, zn as XlvError, zt as checkOutputPositions } from "./options-Cxt-LJE4.js";
|
|
2
2
|
import { Deflate, Inflate } from "fflate";
|
|
3
3
|
// src/zip/stream.ts
|
|
4
4
|
/**
|
|
@@ -396,7 +396,8 @@ function createZipStream(entries, level = 6) {
|
|
|
396
396
|
// src/stream-read.ts
|
|
397
397
|
const GiB = 1073741824;
|
|
398
398
|
/**
|
|
399
|
-
* Limits that differ in streaming mode (D-401): `maxCells` does not apply
|
|
399
|
+
* Limits that differ in streaming mode (D-401): `maxCells` does not apply (but one `<row>` element
|
|
400
|
+
* may hold at most 16 384 cells, D-J01), and single parts may
|
|
400
401
|
* decompress to 4 GiB (8 GiB in total). All other `DEFAULT_READ_LIMITS` stay.
|
|
401
402
|
*
|
|
402
403
|
* @example
|
|
@@ -423,32 +424,69 @@ var PreloadedZip = class {
|
|
|
423
424
|
entries;
|
|
424
425
|
bytesRead = 0;
|
|
425
426
|
data = /* @__PURE__ */ new Map();
|
|
427
|
+
/**
|
|
428
|
+
* ZIP entries by the part name they map to, as openPackage maps them: percent-encoded
|
|
429
|
+
* non-ASCII characters of an item name are decoded (Part 2 §7.3.5; F-OPC-12).
|
|
430
|
+
*/
|
|
431
|
+
byPart = /* @__PURE__ */ new Map();
|
|
426
432
|
constructor(zip, maxBufferedBytes) {
|
|
427
433
|
this.zip = zip;
|
|
428
434
|
this.maxBufferedBytes = maxBufferedBytes;
|
|
429
435
|
this.entries = zip.entries;
|
|
436
|
+
for (const entry of zip.entries) {
|
|
437
|
+
if (entry.name.endsWith("/")) continue;
|
|
438
|
+
const key = partKey(zipNameToPartName(entry.name));
|
|
439
|
+
if (!this.byPart.has(key)) this.byPart.set(key, entry);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** The ZIP entry that stores a part, or undefined. */
|
|
443
|
+
entryOf(partName) {
|
|
444
|
+
return this.byPart.get(partKey(partName));
|
|
430
445
|
}
|
|
431
|
-
async load(
|
|
432
|
-
for (const
|
|
433
|
-
const entry = this.zip.find(name);
|
|
446
|
+
async load(entries) {
|
|
447
|
+
for (const entry of entries) {
|
|
434
448
|
if (entry === void 0 || this.data.has(entry.name)) continue;
|
|
435
449
|
const bytes = await this.zip.read(entry, this.maxBufferedBytes);
|
|
436
450
|
this.data.set(entry.name, bytes);
|
|
437
451
|
this.bytesRead += bytes.length;
|
|
438
452
|
}
|
|
439
453
|
}
|
|
454
|
+
/** Loads the parts with these names that exist. */
|
|
455
|
+
async loadParts(partNames) {
|
|
456
|
+
await this.load([...partNames].map((name) => this.entryOf(name)));
|
|
457
|
+
}
|
|
458
|
+
/** Loads the Relationships parts of these sources (null: the package) that exist. */
|
|
459
|
+
async loadRelationships(sources) {
|
|
460
|
+
await this.loadParts([...sources].map(relationshipsPartName));
|
|
461
|
+
}
|
|
440
462
|
find(name) {
|
|
441
463
|
return this.zip.find(name);
|
|
442
464
|
}
|
|
443
465
|
read(entry) {
|
|
444
466
|
const name = typeof entry === "string" ? this.zip.find(entry)?.name ?? entry : entry.name;
|
|
445
467
|
const bytes = this.data.get(name);
|
|
446
|
-
if (bytes === void 0) throw new
|
|
468
|
+
if (bytes === void 0) throw new XlvError("XLV_E_INTERNAL", `The ZIP entry "${clip(name)}" was read before it was loaded.`, {
|
|
469
|
+
location: { part: `/${clip(name)}` },
|
|
470
|
+
hint: "This is a bug in xlvane. Please report it, with the file if you can share it; readWorkbook reads the file meanwhile."
|
|
471
|
+
});
|
|
447
472
|
return bytes;
|
|
448
473
|
}
|
|
449
474
|
};
|
|
450
|
-
|
|
451
|
-
|
|
475
|
+
/**
|
|
476
|
+
* The internal targets of the workbook part's relationships: the sheets among them, whose
|
|
477
|
+
* Relationships parts analyzeWorkbook reads. Only these, and not every .rels entry of the ZIP,
|
|
478
|
+
* are loaded (D-H01).
|
|
479
|
+
*/
|
|
480
|
+
function workbookTargets(pkg, workbookPart) {
|
|
481
|
+
const seen = /* @__PURE__ */ new Set();
|
|
482
|
+
const out = [];
|
|
483
|
+
for (const rel of pkg.relationships(workbookPart)) {
|
|
484
|
+
const key = partKey(rel.target);
|
|
485
|
+
if (rel.external || seen.has(key) || !pkg.hasPart(rel.target)) continue;
|
|
486
|
+
seen.add(key);
|
|
487
|
+
out.push(rel.target);
|
|
488
|
+
}
|
|
489
|
+
return out;
|
|
452
490
|
}
|
|
453
491
|
function checkSource(source) {
|
|
454
492
|
if (source instanceof Uint8Array || source instanceof ArrayBuffer) return source;
|
|
@@ -486,9 +524,15 @@ async function openWorkbookStream(source, options) {
|
|
|
486
524
|
});
|
|
487
525
|
const zip = await openZipSource(input, limits);
|
|
488
526
|
const preloaded = new PreloadedZip(zip, userLimits?.maxEntryBytes ?? DEFAULT_READ_LIMITS.maxEntryBytes);
|
|
489
|
-
await preloaded.load(zip.entries.filter((e) => e.name.toLowerCase() === "[content_types].xml"
|
|
527
|
+
await preloaded.load(zip.entries.filter((e) => e.name.toLowerCase() === "[content_types].xml"));
|
|
528
|
+
await preloaded.loadRelationships([null]);
|
|
490
529
|
const pkg = openPackage(preloaded, limits);
|
|
491
|
-
|
|
530
|
+
const workbookPart = pkg.relationships(null).find((r) => !r.external && classifyRelationship(r.type)?.name === "officeDocument")?.target;
|
|
531
|
+
if (workbookPart !== void 0) {
|
|
532
|
+
await preloaded.loadRelationships([workbookPart]);
|
|
533
|
+
await preloaded.loadParts(workbookPartsToLoad(pkg));
|
|
534
|
+
await preloaded.loadRelationships(workbookTargets(pkg, workbookPart));
|
|
535
|
+
}
|
|
492
536
|
const warnings = new WarningCollector();
|
|
493
537
|
const info = analyzeWorkbook(pkg, limits, warnings);
|
|
494
538
|
const formatCache = /* @__PURE__ */ new Map();
|
|
@@ -504,7 +548,7 @@ async function openWorkbookStream(source, options) {
|
|
|
504
548
|
const cached = noteCache.get(entry.part);
|
|
505
549
|
if (cached !== void 0) return cached;
|
|
506
550
|
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
|
-
if (parts.length > 0) await preloaded.
|
|
551
|
+
if (parts.length > 0) await preloaded.loadParts(parts.map((r) => r.target));
|
|
508
552
|
const notes = Object.freeze(readNotes(pkg, entry.part, entry.name, limits, warnings, entry.foreignParts));
|
|
509
553
|
noteCache.set(entry.part, notes);
|
|
510
554
|
return notes;
|
|
@@ -553,7 +597,15 @@ async function openWorkbookStream(source, options) {
|
|
|
553
597
|
part,
|
|
554
598
|
limits
|
|
555
599
|
});
|
|
556
|
-
const
|
|
600
|
+
const stored = preloaded.entryOf(part);
|
|
601
|
+
if (stored === void 0) throw new XlvError("XLV_E_PART_MISSING", `The part ${clip(part)} of sheet "${entry.name}" is not in the archive.`, {
|
|
602
|
+
location: {
|
|
603
|
+
part: clip(part),
|
|
604
|
+
sheet: entry.name
|
|
605
|
+
},
|
|
606
|
+
hint: "The workbook is incomplete. Re-save it in a spreadsheet application."
|
|
607
|
+
});
|
|
608
|
+
const reader = zip.stream(stored).getReader();
|
|
557
609
|
try {
|
|
558
610
|
for (;;) {
|
|
559
611
|
const { done, value } = await reader.read();
|
|
@@ -640,6 +692,7 @@ async function* readTableStream(source, options) {
|
|
|
640
692
|
const limits = o.get("limits");
|
|
641
693
|
const book = await openWorkbookStream(source, limits === void 0 ? void 0 : { limits });
|
|
642
694
|
const outputCells = limits?.maxCells ?? DEFAULT_READ_LIMITS.maxCells;
|
|
695
|
+
const keyTextLimit = limits?.maxSharedStringChars ?? DEFAULT_READ_LIMITS.maxSharedStringChars;
|
|
643
696
|
const sheetName = o.get("sheet") ?? (book.sheets.find((s) => s.kind === "worksheet" && s.state === "visible") ?? book.sheets[0])?.name;
|
|
644
697
|
if (sheetName === void 0) return;
|
|
645
698
|
const buffered = [];
|
|
@@ -692,6 +745,16 @@ async function* readTableStream(source, options) {
|
|
|
692
745
|
};
|
|
693
746
|
let positions = 0;
|
|
694
747
|
let cellsRead = 0;
|
|
748
|
+
let byKey = null;
|
|
749
|
+
let byKeyOf = null;
|
|
750
|
+
const columnOf = (cols, key) => {
|
|
751
|
+
if (byKey === null || byKeyOf !== cols) {
|
|
752
|
+
byKey = /* @__PURE__ */ new Map();
|
|
753
|
+
for (const c of cols) if (!byKey.has(c.key)) byKey.set(c.key, c);
|
|
754
|
+
byKeyOf = cols;
|
|
755
|
+
}
|
|
756
|
+
return byKey.get(key);
|
|
757
|
+
};
|
|
695
758
|
const emit = async (r, cols) => {
|
|
696
759
|
if (headerRow !== null && r.number <= headerRow) return null;
|
|
697
760
|
cellsRead += r.cells.length;
|
|
@@ -699,6 +762,7 @@ async function* readTableStream(source, options) {
|
|
|
699
762
|
checkOutputPositions(positions, cellsRead, outputCells, "The table", sheetName);
|
|
700
763
|
const byCol = new Map(r.cells.map((c) => [c.col, c]));
|
|
701
764
|
if (cols.every((c) => byCol.get(c.col)?.value == null)) return null;
|
|
765
|
+
checkKeyText(cols, 1, keyTextLimit, sheetName);
|
|
702
766
|
const row = {};
|
|
703
767
|
for (const c of cols) setOwn(row, c.key, value(byCol.get(c.col)));
|
|
704
768
|
if (schema === void 0) return {
|
|
@@ -717,7 +781,7 @@ async function* readTableStream(source, options) {
|
|
|
717
781
|
const key = typeof seg === "object" && seg !== null ? seg.key : seg;
|
|
718
782
|
return typeof key === "number" ? key : String(key);
|
|
719
783
|
});
|
|
720
|
-
const column = typeof path[0] === "string" ? cols
|
|
784
|
+
const column = typeof path[0] === "string" ? columnOf(cols, path[0]) : void 0;
|
|
721
785
|
return {
|
|
722
786
|
row: r.number,
|
|
723
787
|
cell: column ? formatCellRef(r.number, column.col) : null,
|
|
@@ -833,6 +897,7 @@ function writeWorkbookStream(options) {
|
|
|
833
897
|
details: { option: "definedNames" }
|
|
834
898
|
});
|
|
835
899
|
const names = [];
|
|
900
|
+
const nameKeys = /* @__PURE__ */ new Map();
|
|
836
901
|
rawNames.forEach((raw, i) => {
|
|
837
902
|
const r = readOptions(raw, [
|
|
838
903
|
"name",
|
|
@@ -843,8 +908,9 @@ function writeWorkbookStream(options) {
|
|
|
843
908
|
], `definedNames[${String(i)}]`);
|
|
844
909
|
const { name, refersTo, ...rest } = Object.fromEntries(r);
|
|
845
910
|
const n = checkDefinedName(name, refersTo, rest, null);
|
|
846
|
-
checkUniqueName(n,
|
|
911
|
+
checkUniqueName(n, nameKeys);
|
|
847
912
|
names.push(n);
|
|
913
|
+
nameKeys.set(nameScopeKey(n), n);
|
|
848
914
|
});
|
|
849
915
|
const addNames = () => {
|
|
850
916
|
for (const n of names) book.defineName(n.name, n.refersTo, {
|
|
@@ -886,6 +952,7 @@ function writeWorkbookStream(options) {
|
|
|
886
952
|
"tables",
|
|
887
953
|
"images",
|
|
888
954
|
"background",
|
|
955
|
+
"charts",
|
|
889
956
|
"rows"
|
|
890
957
|
], `sheets[${String(i)}]`);
|
|
891
958
|
const state = s.get("state");
|
|
@@ -942,7 +1009,7 @@ function writeWorkbookStream(options) {
|
|
|
942
1009
|
for (const v of listOf("tables", "{ name: \"Sales\", ref: \"A1:B10\", columns: [{ name: \"Region\" }, { name: \"Revenue\" }] }")) {
|
|
943
1010
|
const t = normalizeTable(v, sheet.name, true);
|
|
944
1011
|
book.checkTable(t, sheet);
|
|
945
|
-
sheet.
|
|
1012
|
+
sheet.pushTable(t);
|
|
946
1013
|
}
|
|
947
1014
|
const merges = s.get("merges");
|
|
948
1015
|
if (merges !== void 0) {
|
|
@@ -992,6 +1059,7 @@ function writeWorkbookStream(options) {
|
|
|
992
1059
|
sheet.addImage(image, anchor, options);
|
|
993
1060
|
}
|
|
994
1061
|
if (s.has("background")) sheet.background(s.get("background"));
|
|
1062
|
+
for (const v of listOf("charts", "{ type: \"column\", anchor: \"E2:L18\", series: [{ values: \"Data!B2:B13\" }] }")) sheet.addChart(v);
|
|
995
1063
|
return {
|
|
996
1064
|
spec: raw,
|
|
997
1065
|
sheet,
|
|
@@ -1008,6 +1076,12 @@ function writeWorkbookStream(options) {
|
|
|
1008
1076
|
const prepared = dynamic ? [] : specs.map(prepare);
|
|
1009
1077
|
if (!dynamic && !prepared.some((p) => p.sheet.state === "visible")) throw noVisibleSheet();
|
|
1010
1078
|
if (!dynamic) addNames();
|
|
1079
|
+
if (!dynamic) {
|
|
1080
|
+
const lookup = chartSheetNames(prepared.map((q) => q.sheet.name));
|
|
1081
|
+
for (const p of prepared) for (const c of p.sheet.chartList) resolveChartSheets(c.chart, lookup, p.sheet.name);
|
|
1082
|
+
}
|
|
1083
|
+
/** Charts in workbook order (CH-01); their parts are written once every sheet name is known. */
|
|
1084
|
+
const chartQueue = [];
|
|
1011
1085
|
const strings = shared ? createSharedStrings() : null;
|
|
1012
1086
|
const styleCache = /* @__PURE__ */ new WeakMap();
|
|
1013
1087
|
const dateStyle = /* @__PURE__ */ new Map();
|
|
@@ -1162,7 +1236,19 @@ function writeWorkbookStream(options) {
|
|
|
1162
1236
|
conditionalFormats: p.sheet.conditionalList,
|
|
1163
1237
|
dataValidations: p.sheet.validationList
|
|
1164
1238
|
}, n);
|
|
1165
|
-
const
|
|
1239
|
+
const frames = p.sheet.chartList.map((c) => {
|
|
1240
|
+
chartQueue.push({
|
|
1241
|
+
chart: c.chart,
|
|
1242
|
+
sheet: p.sheet.name
|
|
1243
|
+
});
|
|
1244
|
+
return {
|
|
1245
|
+
...resolveAnchor(c.anchor, metricsOf(p.sheet.columnMap, p.sheet.rowMap)),
|
|
1246
|
+
name: c.chart.name,
|
|
1247
|
+
description: c.chart.description,
|
|
1248
|
+
target: `../charts/chart${String(chartQueue.length)}.xml`
|
|
1249
|
+
};
|
|
1250
|
+
});
|
|
1251
|
+
const drawing = drawingParts(p.sheet.imageList.map((i) => resolveImage(i, metricsOf(p.sheet.columnMap, p.sheet.rowMap))), p.sheet.backgroundImage, media, ++drawingCount, p.rels, frames);
|
|
1166
1252
|
if (drawing.part === null) drawingCount--;
|
|
1167
1253
|
p.drawing = drawing.part;
|
|
1168
1254
|
const sheetTables = tableParts(p.sheet.tableList, tableCount + 1, p.rels);
|
|
@@ -1279,10 +1365,16 @@ function writeWorkbookStream(options) {
|
|
|
1279
1365
|
if (prepared.length === 0) throw new XlvError("XLV_E_INVALID_INPUT", "The workbook has no sheets.", { hint: "Add at least one sheet before closing the stream." });
|
|
1280
1366
|
if (!selected) throw noVisibleSheet();
|
|
1281
1367
|
if (dynamic) addNames();
|
|
1368
|
+
const names = chartSheetNames(prepared.map((p) => p.sheet.name));
|
|
1369
|
+
const chartParts = chartQueue.map((c, k) => ({
|
|
1370
|
+
name: chartPartName(k + 1),
|
|
1371
|
+
xml: chartPartXml(resolveChartSheets(c.chart, names, c.sheet), null)
|
|
1372
|
+
}));
|
|
1282
1373
|
const app = appXml() !== null;
|
|
1283
1374
|
const overrides = [
|
|
1284
1375
|
...tableTypes(),
|
|
1285
1376
|
...prepared.flatMap((p) => p.drawing === null ? [] : [[p.drawing.name, DRAWING_CONTENT_TYPE]]),
|
|
1377
|
+
...chartParts.map((c) => [c.name, CHART_CONTENT_TYPE]),
|
|
1286
1378
|
...media.entries().map((m) => [m.name, IMAGE_CONTENT_TYPES[m.name.slice(m.name.lastIndexOf(".") + 1)]])
|
|
1287
1379
|
];
|
|
1288
1380
|
yield {
|
|
@@ -1294,6 +1386,10 @@ function writeWorkbookStream(options) {
|
|
|
1294
1386
|
data: encoder.encode(rootRelsXml(core !== null, app))
|
|
1295
1387
|
};
|
|
1296
1388
|
yield* media.entries();
|
|
1389
|
+
for (const c of chartParts) yield {
|
|
1390
|
+
name: c.name,
|
|
1391
|
+
data: encoder.encode(c.xml)
|
|
1392
|
+
};
|
|
1297
1393
|
yield* closingParts();
|
|
1298
1394
|
}
|
|
1299
1395
|
return createZipStream(entries(), level);
|