@particle-academy/holy-sheet 2.10.1 → 2.10.2
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/CHANGELOG.md +23 -0
- package/README.md +7 -0
- package/dist/index.cjs +299 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +299 -99
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -242,12 +242,12 @@ function parseRows(csv, delimiter, quote2) {
|
|
|
242
242
|
// src/workbook/cell-address.ts
|
|
243
243
|
var CellAddress = {
|
|
244
244
|
/** 0-based column index → Excel-style letters. */
|
|
245
|
-
letter(
|
|
246
|
-
if (
|
|
247
|
-
throw new Error(`[holy-sheet] column index must be \u2265 0, got ${
|
|
245
|
+
letter(index2) {
|
|
246
|
+
if (index2 < 0) {
|
|
247
|
+
throw new Error(`[holy-sheet] column index must be \u2265 0, got ${index2}`);
|
|
248
248
|
}
|
|
249
249
|
let letters = "";
|
|
250
|
-
let n =
|
|
250
|
+
let n = index2;
|
|
251
251
|
do {
|
|
252
252
|
letters = String.fromCharCode(65 + n % 26) + letters;
|
|
253
253
|
n = Math.floor(n / 26) - 1;
|
|
@@ -300,8 +300,8 @@ var ColumnWidths = {
|
|
|
300
300
|
return Number.isInteger(key) && key >= 0 && key <= ColumnWidths.MAX_INDEX ? key : null;
|
|
301
301
|
}
|
|
302
302
|
if (typeof key === "string" && /^[0-9]{1,5}$/.test(key)) {
|
|
303
|
-
const
|
|
304
|
-
return
|
|
303
|
+
const index2 = parseInt(key, 10);
|
|
304
|
+
return index2 <= ColumnWidths.MAX_INDEX ? index2 : null;
|
|
305
305
|
}
|
|
306
306
|
return null;
|
|
307
307
|
},
|
|
@@ -560,6 +560,14 @@ var DateConverter = {
|
|
|
560
560
|
};
|
|
561
561
|
|
|
562
562
|
// src/exceptions.ts
|
|
563
|
+
var ArchiveRefused = class _ArchiveRefused extends Error {
|
|
564
|
+
constructor(reason) {
|
|
565
|
+
super(`Source archive refused: ${reason}`);
|
|
566
|
+
this.reason = reason;
|
|
567
|
+
this.name = "ArchiveRefused";
|
|
568
|
+
Object.setPrototypeOf(this, _ArchiveRefused.prototype);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
563
571
|
var SchemaException = class _SchemaException extends Error {
|
|
564
572
|
constructor(errors, message) {
|
|
565
573
|
super(message ?? summarize(errors));
|
|
@@ -800,23 +808,23 @@ var Normalizer = class {
|
|
|
800
808
|
for (const [colIdx, columnDef] of (sheet.columns ?? []).entries()) {
|
|
801
809
|
if (columnDef === null || typeof columnDef !== "object") continue;
|
|
802
810
|
if (columnDef.width === void 0) continue;
|
|
803
|
-
const
|
|
811
|
+
const index2 = ColumnWidths.index(colIdx);
|
|
804
812
|
const width = ColumnWidths.width(columnDef.width);
|
|
805
|
-
if (
|
|
806
|
-
out.set(
|
|
813
|
+
if (index2 === null || width === null) continue;
|
|
814
|
+
out.set(index2, width);
|
|
807
815
|
}
|
|
808
|
-
for (const [
|
|
809
|
-
out.set(
|
|
816
|
+
for (const [index2, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
|
|
817
|
+
out.set(index2, width);
|
|
810
818
|
}
|
|
811
819
|
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
812
820
|
}
|
|
813
821
|
normalizeColumnWidths(widths) {
|
|
814
822
|
const out = /* @__PURE__ */ new Map();
|
|
815
823
|
for (const [key, px] of Object.entries(widths)) {
|
|
816
|
-
const
|
|
824
|
+
const index2 = ColumnWidths.index(key);
|
|
817
825
|
const width = ColumnWidths.width(px);
|
|
818
|
-
if (
|
|
819
|
-
out.set(
|
|
826
|
+
if (index2 === null || width === null) continue;
|
|
827
|
+
out.set(index2, width);
|
|
820
828
|
}
|
|
821
829
|
return out;
|
|
822
830
|
}
|
|
@@ -928,14 +936,14 @@ function normalizePoints(points2, count) {
|
|
|
928
936
|
const byIndex = /* @__PURE__ */ new Map();
|
|
929
937
|
for (const p of Object.values(points2)) {
|
|
930
938
|
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
931
|
-
const
|
|
939
|
+
const index2 = rec ? rec.index : void 0;
|
|
932
940
|
const color2 = rec ? rec.color : void 0;
|
|
933
941
|
const m = typeof color2 === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(color2) : null;
|
|
934
|
-
if (typeof
|
|
935
|
-
byIndex.set(
|
|
942
|
+
if (typeof index2 === "number" && Number.isInteger(index2) && index2 >= 0 && index2 < count && m) {
|
|
943
|
+
byIndex.set(index2, m[1].toUpperCase());
|
|
936
944
|
}
|
|
937
945
|
}
|
|
938
|
-
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([
|
|
946
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index2, color2]) => ({ index: index2, color: color2 }));
|
|
939
947
|
}
|
|
940
948
|
var CHART_TYPES = ["column", "bar", "line", "pie"];
|
|
941
949
|
function normalizeCharts(charts, sheetName2, cells) {
|
|
@@ -1069,13 +1077,13 @@ var FormulaLinter = class {
|
|
|
1069
1077
|
return issues;
|
|
1070
1078
|
}
|
|
1071
1079
|
buildIndex(wb) {
|
|
1072
|
-
const
|
|
1080
|
+
const index2 = /* @__PURE__ */ new Map();
|
|
1073
1081
|
for (const sheet of wb.sheets) {
|
|
1074
1082
|
for (const [address, cell] of sheet.cells) {
|
|
1075
|
-
|
|
1083
|
+
index2.set(sheet.name + "!" + address, cell);
|
|
1076
1084
|
}
|
|
1077
1085
|
}
|
|
1078
|
-
return
|
|
1086
|
+
return index2;
|
|
1079
1087
|
}
|
|
1080
1088
|
isError(v) {
|
|
1081
1089
|
return typeof v === "string" && v.length > 0 && v[0] === "#";
|
|
@@ -1589,21 +1597,21 @@ var Repairer = class {
|
|
|
1589
1597
|
const widths = {};
|
|
1590
1598
|
let changed = false;
|
|
1591
1599
|
for (const [key, px] of Object.entries(sheet.columnWidths)) {
|
|
1592
|
-
let
|
|
1593
|
-
if (
|
|
1600
|
+
let index2 = ColumnWidths.index(key);
|
|
1601
|
+
if (index2 === null) {
|
|
1594
1602
|
const letters = ColumnWidths.fromLetters(key);
|
|
1595
1603
|
if (letters !== null) {
|
|
1596
|
-
|
|
1597
|
-
this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${
|
|
1604
|
+
index2 = letters;
|
|
1605
|
+
this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${index2}`);
|
|
1598
1606
|
changed = true;
|
|
1599
1607
|
}
|
|
1600
1608
|
}
|
|
1601
|
-
if (
|
|
1609
|
+
if (index2 === null || ColumnWidths.width(px) === null) {
|
|
1602
1610
|
this.repairs.push(`dropped '${path}.columnWidths.${key}' (not a column index and a width)`);
|
|
1603
1611
|
changed = true;
|
|
1604
1612
|
continue;
|
|
1605
1613
|
}
|
|
1606
|
-
widths[String(
|
|
1614
|
+
widths[String(index2)] = px;
|
|
1607
1615
|
}
|
|
1608
1616
|
if (changed) {
|
|
1609
1617
|
sheet.columnWidths = widths;
|
|
@@ -2027,14 +2035,14 @@ function validatePoints(points2, at2, values2) {
|
|
|
2027
2035
|
const errors = [];
|
|
2028
2036
|
points2.forEach((p, k) => {
|
|
2029
2037
|
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
2030
|
-
const
|
|
2038
|
+
const index2 = rec ? rec.index ?? null : null;
|
|
2031
2039
|
const color2 = rec ? rec.color ?? null : null;
|
|
2032
|
-
if (typeof
|
|
2040
|
+
if (typeof index2 !== "number" || !Number.isInteger(index2) || index2 < 0 || index2 >= count) {
|
|
2033
2041
|
errors.push(error(
|
|
2034
2042
|
`${at2}[${k}].index`,
|
|
2035
2043
|
"an index within values (0 to " + Math.max(0, count - 1) + ")",
|
|
2036
|
-
typeOf(
|
|
2037
|
-
|
|
2044
|
+
typeOf(index2),
|
|
2045
|
+
index2,
|
|
2038
2046
|
'The 0-based position of the point in its values range: "B5:B7" has points 0, 1 and 2.'
|
|
2039
2047
|
));
|
|
2040
2048
|
}
|
|
@@ -2429,8 +2437,17 @@ function buildTree(t, lengths, off, num) {
|
|
|
2429
2437
|
let i;
|
|
2430
2438
|
let sum = 0;
|
|
2431
2439
|
for (i = 0; i < 16; i++) t.table[i] = 0;
|
|
2432
|
-
for (i = 0; i < num; i++)
|
|
2440
|
+
for (i = 0; i < num; i++) {
|
|
2441
|
+
const length = lengths[off + i];
|
|
2442
|
+
if (length === void 0 || length > 15) throw new Error("holy-sheet: invalid DEFLATE tree");
|
|
2443
|
+
t.table[length]++;
|
|
2444
|
+
}
|
|
2433
2445
|
t.table[0] = 0;
|
|
2446
|
+
let slots = 1;
|
|
2447
|
+
for (i = 1; i <= 15; i++) {
|
|
2448
|
+
slots = slots * 2 - t.table[i];
|
|
2449
|
+
if (slots < 0) throw new Error("holy-sheet: oversubscribed DEFLATE tree");
|
|
2450
|
+
}
|
|
2434
2451
|
for (i = 0; i < 16; i++) {
|
|
2435
2452
|
OFFS[i] = sum;
|
|
2436
2453
|
sum += t.table[i];
|
|
@@ -2450,6 +2467,7 @@ var Reader = class {
|
|
|
2450
2467
|
};
|
|
2451
2468
|
function getBit(d) {
|
|
2452
2469
|
if (d.bitcount-- === 0) {
|
|
2470
|
+
if (d.index >= d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
|
|
2453
2471
|
d.tag = d.source[d.index++];
|
|
2454
2472
|
d.bitcount = 7;
|
|
2455
2473
|
}
|
|
@@ -2470,6 +2488,7 @@ function decodeSymbol(d, t) {
|
|
|
2470
2488
|
do {
|
|
2471
2489
|
cur = 2 * cur + getBit(d);
|
|
2472
2490
|
len++;
|
|
2491
|
+
if (len > 15) throw new Error("holy-sheet: invalid DEFLATE code");
|
|
2473
2492
|
sum += t.table[len];
|
|
2474
2493
|
cur -= t.table[len];
|
|
2475
2494
|
} while (cur >= 0);
|
|
@@ -2480,6 +2499,7 @@ function decodeTrees(d, lt, dt) {
|
|
|
2480
2499
|
const hlit = readBits(d, 5, 257);
|
|
2481
2500
|
const hdist = readBits(d, 5, 1);
|
|
2482
2501
|
const hclen = readBits(d, 4, 4);
|
|
2502
|
+
if (hlit > 286) throw new Error("holy-sheet: invalid DEFLATE literal count");
|
|
2483
2503
|
let i;
|
|
2484
2504
|
for (i = 0; i < 19; i++) lengths[i] = 0;
|
|
2485
2505
|
for (i = 0; i < hclen; i++) {
|
|
@@ -2492,32 +2512,44 @@ function decodeTrees(d, lt, dt) {
|
|
|
2492
2512
|
const sym = decodeSymbol(d, codeTree);
|
|
2493
2513
|
switch (sym) {
|
|
2494
2514
|
case 16: {
|
|
2515
|
+
if (num === 0) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2495
2516
|
const prev = lengths[num - 1];
|
|
2496
|
-
|
|
2517
|
+
const repeat = readBits(d, 2, 3);
|
|
2518
|
+
if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2519
|
+
for (let length = repeat; length; length--) lengths[num++] = prev;
|
|
2497
2520
|
break;
|
|
2498
2521
|
}
|
|
2499
|
-
case 17:
|
|
2500
|
-
|
|
2522
|
+
case 17: {
|
|
2523
|
+
const repeat = readBits(d, 3, 3);
|
|
2524
|
+
if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2525
|
+
for (let length = repeat; length; length--) lengths[num++] = 0;
|
|
2501
2526
|
break;
|
|
2502
|
-
|
|
2503
|
-
|
|
2527
|
+
}
|
|
2528
|
+
case 18: {
|
|
2529
|
+
const repeat = readBits(d, 7, 11);
|
|
2530
|
+
if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2531
|
+
for (let length = repeat; length; length--) lengths[num++] = 0;
|
|
2504
2532
|
break;
|
|
2533
|
+
}
|
|
2505
2534
|
default:
|
|
2506
2535
|
lengths[num++] = sym;
|
|
2507
2536
|
break;
|
|
2508
2537
|
}
|
|
2509
2538
|
}
|
|
2539
|
+
if (lengths[256] === 0) throw new Error("holy-sheet: missing DEFLATE end code");
|
|
2510
2540
|
buildTree(lt, lengths, 0, hlit);
|
|
2511
2541
|
buildTree(dt, lengths, hlit, hdist);
|
|
2512
2542
|
}
|
|
2513
2543
|
var Out = class {
|
|
2514
|
-
constructor() {
|
|
2515
|
-
this.
|
|
2544
|
+
constructor(limit) {
|
|
2545
|
+
this.limit = limit;
|
|
2516
2546
|
this.len = 0;
|
|
2547
|
+
this.buf = new Uint8Array(Math.min(1024, limit));
|
|
2517
2548
|
}
|
|
2518
2549
|
push(b) {
|
|
2550
|
+
if (this.len >= this.limit) throw new Error("holy-sheet: DEFLATE output limit exceeded");
|
|
2519
2551
|
if (this.len >= this.buf.length) {
|
|
2520
|
-
const next = new Uint8Array(this.buf.length * 2);
|
|
2552
|
+
const next = new Uint8Array(Math.min(this.limit, Math.max(1, this.buf.length * 2)));
|
|
2521
2553
|
next.set(this.buf);
|
|
2522
2554
|
this.buf = next;
|
|
2523
2555
|
}
|
|
@@ -2532,9 +2564,12 @@ function inflateBlockData(d, out, lt, dt) {
|
|
|
2532
2564
|
out.push(sym);
|
|
2533
2565
|
} else {
|
|
2534
2566
|
const lengthSym = sym - 257;
|
|
2567
|
+
if (lengthSym < 0 || lengthSym >= LENGTH_BITS.length) throw new Error("holy-sheet: invalid DEFLATE length");
|
|
2535
2568
|
const length = readBits(d, LENGTH_BITS[lengthSym], LENGTH_BASE[lengthSym]);
|
|
2536
2569
|
const distSym = decodeSymbol(d, dt);
|
|
2570
|
+
if (distSym < 0 || distSym >= DIST_BITS.length) throw new Error("holy-sheet: invalid DEFLATE distance");
|
|
2537
2571
|
const dist = readBits(d, DIST_BITS[distSym], DIST_BASE[distSym]);
|
|
2572
|
+
if (dist > out.len) throw new Error("holy-sheet: DEFLATE distance before output");
|
|
2538
2573
|
const offs = out.len - dist;
|
|
2539
2574
|
for (let i = 0; i < length; i++) out.push(out.buf[offs + i]);
|
|
2540
2575
|
}
|
|
@@ -2542,13 +2577,20 @@ function inflateBlockData(d, out, lt, dt) {
|
|
|
2542
2577
|
}
|
|
2543
2578
|
function inflateUncompressedBlock(d, out) {
|
|
2544
2579
|
d.bitcount = 0;
|
|
2580
|
+
if (d.index + 4 > d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
|
|
2545
2581
|
let length = d.source[d.index + 1] * 256 + d.source[d.index];
|
|
2582
|
+
const complement = d.source[d.index + 3] * 256 + d.source[d.index + 2];
|
|
2583
|
+
if ((length ^ complement) !== 65535) throw new Error("holy-sheet: invalid DEFLATE stored length");
|
|
2546
2584
|
d.index += 4;
|
|
2585
|
+
if (d.index + length > d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
|
|
2547
2586
|
for (; length; length--) out.push(d.source[d.index++]);
|
|
2548
2587
|
}
|
|
2549
|
-
function inflateRaw(source) {
|
|
2588
|
+
function inflateRaw(source, maxOutputBytes = Number.POSITIVE_INFINITY) {
|
|
2589
|
+
if (maxOutputBytes !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 0)) {
|
|
2590
|
+
throw new Error("holy-sheet: invalid DEFLATE output limit");
|
|
2591
|
+
}
|
|
2550
2592
|
const d = new Reader(source);
|
|
2551
|
-
const out = new Out();
|
|
2593
|
+
const out = new Out(maxOutputBytes);
|
|
2552
2594
|
const lt = new Tree();
|
|
2553
2595
|
const dt = new Tree();
|
|
2554
2596
|
let bfinal;
|
|
@@ -2570,41 +2612,166 @@ function inflateRaw(source) {
|
|
|
2570
2612
|
return out.buf.subarray(0, out.len);
|
|
2571
2613
|
}
|
|
2572
2614
|
|
|
2573
|
-
// src/zip/
|
|
2574
|
-
var
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2615
|
+
// src/zip/source-archive.ts
|
|
2616
|
+
var DEFAULT_ARCHIVE_LIMITS = Object.freeze({
|
|
2617
|
+
maxArchiveBytes: 67108864,
|
|
2618
|
+
maxEntries: 16384,
|
|
2619
|
+
maxPartBytes: 67108864,
|
|
2620
|
+
maxExpandedBytes: 268435456,
|
|
2621
|
+
maxCompressionRatio: 1e3
|
|
2622
|
+
});
|
|
2623
|
+
var corrupt = () => {
|
|
2624
|
+
throw new ArchiveRefused("archive_corrupt");
|
|
2625
|
+
};
|
|
2626
|
+
var SourceArchive = class _SourceArchive {
|
|
2627
|
+
constructor(original, parts) {
|
|
2628
|
+
this.original = original;
|
|
2629
|
+
this.parts = parts;
|
|
2630
|
+
}
|
|
2631
|
+
static open(input, options = {}) {
|
|
2632
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2633
|
+
if (!Object.prototype.hasOwnProperty.call(DEFAULT_ARCHIVE_LIMITS, key) || !Number.isSafeInteger(value) || value < 1) throw new ArchiveRefused("archive_limits");
|
|
2634
|
+
}
|
|
2635
|
+
const limits = { ...DEFAULT_ARCHIVE_LIMITS, ...options };
|
|
2636
|
+
if (input.length > limits.maxArchiveBytes) throw new ArchiveRefused("archive_limit");
|
|
2637
|
+
const bytes = input.slice();
|
|
2638
|
+
const records = index(bytes, limits);
|
|
2639
|
+
const parts = /* @__PURE__ */ new Map();
|
|
2640
|
+
for (const record of records) {
|
|
2641
|
+
const compressed = bytes.subarray(record.start, record.start + record.compressed);
|
|
2642
|
+
let payload;
|
|
2643
|
+
try {
|
|
2644
|
+
payload = record.method === 0 ? compressed.slice() : inflateRaw(compressed, record.size + 1);
|
|
2645
|
+
} catch {
|
|
2646
|
+
throw new ArchiveRefused("archive_corrupt");
|
|
2647
|
+
}
|
|
2648
|
+
if (payload.length !== record.size || crc32(payload) !== record.crc) corrupt();
|
|
2649
|
+
parts.set(record.name, payload);
|
|
2650
|
+
}
|
|
2651
|
+
return new _SourceArchive(bytes, parts);
|
|
2652
|
+
}
|
|
2653
|
+
names() {
|
|
2654
|
+
return [...this.parts.keys()];
|
|
2655
|
+
}
|
|
2656
|
+
read(name) {
|
|
2657
|
+
return this.parts.get(name)?.slice() ?? null;
|
|
2658
|
+
}
|
|
2659
|
+
toBytes() {
|
|
2660
|
+
return this.original.slice();
|
|
2661
|
+
}
|
|
2662
|
+
};
|
|
2663
|
+
function index(bytes, limits) {
|
|
2664
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2665
|
+
const bounds = (at3, size, end2 = bytes.length) => {
|
|
2666
|
+
if (at3 < 0 || size < 0 || at3 + size > end2 || at3 + size > bytes.length) corrupt();
|
|
2667
|
+
};
|
|
2668
|
+
const u16 = (at3) => {
|
|
2669
|
+
bounds(at3, 2);
|
|
2670
|
+
return view.getUint16(at3, true);
|
|
2671
|
+
};
|
|
2672
|
+
const u32 = (at3) => {
|
|
2673
|
+
bounds(at3, 4);
|
|
2674
|
+
return view.getUint32(at3, true);
|
|
2675
|
+
};
|
|
2676
|
+
let end = -1;
|
|
2677
|
+
for (let at3 = bytes.length - 22; at3 >= Math.max(0, bytes.length - 65557); at3--) {
|
|
2678
|
+
if (u32(at3) === 101010256 && at3 + 22 + u16(at3 + 20) === bytes.length && u32(at3 + 16) + u32(at3 + 12) === at3) {
|
|
2679
|
+
end = at3;
|
|
2581
2680
|
break;
|
|
2582
2681
|
}
|
|
2583
2682
|
}
|
|
2584
|
-
if (
|
|
2585
|
-
const count =
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
const
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2683
|
+
if (end === -1) corrupt();
|
|
2684
|
+
const count = u16(end + 10), directory = u32(end + 16);
|
|
2685
|
+
if (u16(end + 4) !== 0 || u16(end + 6) !== 0 || u16(end + 8) !== count || count === 65535) throw new ArchiveRefused("archive_unsupported");
|
|
2686
|
+
if (count > limits.maxEntries) throw new ArchiveRefused("archive_limit");
|
|
2687
|
+
if (end - directory < count * 46) corrupt();
|
|
2688
|
+
const records = [], seen = /* @__PURE__ */ new Set(), regions = [];
|
|
2689
|
+
let at2 = directory, expanded = 0;
|
|
2690
|
+
const extra = (start, length) => {
|
|
2691
|
+
const finish = start + length;
|
|
2692
|
+
while (start < finish) {
|
|
2693
|
+
bounds(start, 4, finish);
|
|
2694
|
+
const id = u16(start), size = u16(start + 2);
|
|
2695
|
+
bounds(start + 4, size, finish);
|
|
2696
|
+
if (id === 1 || id === 39169) throw new ArchiveRefused("archive_unsupported");
|
|
2697
|
+
start += 4 + size;
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
for (let i = 0; i < count; i++) {
|
|
2701
|
+
bounds(at2, 46, end);
|
|
2702
|
+
if (u32(at2) !== 33639248) corrupt();
|
|
2703
|
+
const flags = u16(at2 + 8), method = u16(at2 + 10), crc = u32(at2 + 16);
|
|
2704
|
+
const compressed = u32(at2 + 20), size = u32(at2 + 24), local = u32(at2 + 42);
|
|
2705
|
+
const nameLength = u16(at2 + 28), extraLength = u16(at2 + 30), commentLength = u16(at2 + 32);
|
|
2706
|
+
if ((flags & -2063) !== 0 || ![0, 8].includes(method) || method === 0 && (flags & 6) !== 0 || u16(at2 + 34) !== 0 || [compressed, size, local].includes(4294967295) || u16(at2 + 6) > 20) throw new ArchiveRefused("archive_unsupported");
|
|
2707
|
+
bounds(at2 + 46, nameLength + extraLength + commentLength, end);
|
|
2708
|
+
const name = partName(bytes.subarray(at2 + 46, at2 + 46 + nameLength), flags);
|
|
2709
|
+
if (seen.has(name)) throw new ArchiveRefused("archive_duplicate");
|
|
2710
|
+
seen.add(name);
|
|
2711
|
+
expanded += size;
|
|
2712
|
+
if (size > limits.maxPartBytes || expanded > limits.maxExpandedBytes || size > Math.max(1, compressed) * limits.maxCompressionRatio) throw new ArchiveRefused("archive_limit");
|
|
2713
|
+
if (method === 0 && compressed !== size) corrupt();
|
|
2714
|
+
extra(at2 + 46 + nameLength, extraLength);
|
|
2715
|
+
bounds(local, 30, directory);
|
|
2716
|
+
if (u32(local) !== 67324752 || u16(local + 4) !== u16(at2 + 6) || u16(local + 6) !== flags || u16(local + 8) !== method) corrupt();
|
|
2717
|
+
const localName2 = u16(local + 26), localExtra = u16(local + 28);
|
|
2718
|
+
const start = local + 30 + localName2 + localExtra;
|
|
2719
|
+
bounds(local + 30, localName2 + localExtra + compressed, directory);
|
|
2720
|
+
const rawName = bytes.subarray(local + 30, local + 30 + localName2);
|
|
2721
|
+
const centralName = bytes.subarray(at2 + 46, at2 + 46 + nameLength);
|
|
2722
|
+
if (rawName.length !== centralName.length || !rawName.every((byte, pos) => byte === centralName[pos])) corrupt();
|
|
2723
|
+
extra(local + 30 + localName2, localExtra);
|
|
2724
|
+
for (const [offset, expected] of [[14, crc], [18, compressed], [22, size]]) {
|
|
2725
|
+
const value = u32(local + offset);
|
|
2726
|
+
if (value !== expected && !((flags & 8) !== 0 && value === 0)) corrupt();
|
|
2727
|
+
}
|
|
2728
|
+
let finish = start + compressed;
|
|
2729
|
+
if ((flags & 8) !== 0) {
|
|
2730
|
+
let found = false;
|
|
2731
|
+
for (const prefix of [0, 4]) {
|
|
2732
|
+
if (finish + prefix + 12 <= directory && (prefix === 0 || u32(finish) === 134695760) && u32(finish + prefix) === crc && u32(finish + prefix + 4) === compressed && u32(finish + prefix + 8) === size) {
|
|
2733
|
+
finish += prefix + 12;
|
|
2734
|
+
found = true;
|
|
2735
|
+
break;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (!found) corrupt();
|
|
2739
|
+
}
|
|
2740
|
+
records.push({ name, method, crc, size, compressed, start });
|
|
2741
|
+
regions.push([local, finish]);
|
|
2742
|
+
at2 += 46 + nameLength + extraLength + commentLength;
|
|
2743
|
+
}
|
|
2744
|
+
if (at2 !== end) corrupt();
|
|
2745
|
+
regions.sort((a, b) => a[0] - b[0]);
|
|
2746
|
+
let previous = 0;
|
|
2747
|
+
for (const [start, finish] of regions) {
|
|
2748
|
+
if (start < previous) corrupt();
|
|
2749
|
+
previous = finish;
|
|
2750
|
+
}
|
|
2751
|
+
return records;
|
|
2752
|
+
}
|
|
2753
|
+
function partName(bytes, flags) {
|
|
2754
|
+
let name;
|
|
2755
|
+
try {
|
|
2756
|
+
name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
2757
|
+
} catch {
|
|
2758
|
+
throw new ArchiveRefused("archive_path");
|
|
2759
|
+
}
|
|
2760
|
+
if (name === "" || name.startsWith("/") || name.includes(String.fromCharCode(92)) || name.includes(":")) throw new ArchiveRefused("archive_path");
|
|
2761
|
+
for (const byte of bytes) {
|
|
2762
|
+
if (byte < 32 || byte === 127) throw new ArchiveRefused("archive_path");
|
|
2763
|
+
if (byte > 127 && (flags & 2048) === 0) throw new ArchiveRefused("archive_unsupported");
|
|
2607
2764
|
}
|
|
2765
|
+
const parts = (name.endsWith("/") ? name.slice(0, -1) : name).split("/");
|
|
2766
|
+
if (parts.some((part2) => part2 === "" || part2 === "." || part2 === "..")) throw new ArchiveRefused("archive_path");
|
|
2767
|
+
return name;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// src/zip/zip-reader.ts
|
|
2771
|
+
function unzipSync(data, limits = {}) {
|
|
2772
|
+
const source = SourceArchive.open(data, limits);
|
|
2773
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2774
|
+
for (const name of source.names()) result[name] = source.read(name);
|
|
2608
2775
|
return result;
|
|
2609
2776
|
}
|
|
2610
2777
|
|
|
@@ -2652,23 +2819,56 @@ var ODS_MIMETYPES = /* @__PURE__ */ new Set([
|
|
|
2652
2819
|
"application/vnd.oasis.opendocument.spreadsheet",
|
|
2653
2820
|
"application/vnd.oasis.opendocument.spreadsheet-template"
|
|
2654
2821
|
]);
|
|
2655
|
-
var
|
|
2822
|
+
var decoder = new TextDecoder();
|
|
2656
2823
|
var FormatSniffer = {
|
|
2657
2824
|
sniff(bytes, path = null) {
|
|
2658
2825
|
let files;
|
|
2659
2826
|
try {
|
|
2660
2827
|
files = unzipSync(bytes);
|
|
2661
2828
|
} catch (e) {
|
|
2829
|
+
if (e instanceof ArchiveRefused && (e.reason !== "archive_corrupt" || bytes[0] === 80 && bytes[1] === 75)) throw e;
|
|
2662
2830
|
if (e instanceof Error && e.message.includes("unsupported zip method")) throw e;
|
|
2663
2831
|
throw UnsupportedFormatException.notAZip(path);
|
|
2664
2832
|
}
|
|
2665
|
-
const mimetype = files["mimetype"] ? phpTrim(
|
|
2833
|
+
const mimetype = files["mimetype"] ? phpTrim(decoder.decode(files["mimetype"])) : null;
|
|
2666
2834
|
if (mimetype !== null && ODS_MIMETYPES.has(mimetype)) return { format: "ods", files };
|
|
2667
2835
|
if (files["xl/workbook.xml"]) return { format: "xlsx", files };
|
|
2668
2836
|
throw UnsupportedFormatException.unknownPackage(path, mimetype);
|
|
2669
2837
|
}
|
|
2670
2838
|
};
|
|
2671
2839
|
|
|
2840
|
+
// src/reader/file-input.ts
|
|
2841
|
+
async function readFileBounded(path) {
|
|
2842
|
+
const fs = await import('fs');
|
|
2843
|
+
if (!fs.statSync(path).isFile()) throw new ArchiveRefused("archive_path");
|
|
2844
|
+
const fd = fs.openSync(path, "r");
|
|
2845
|
+
const limit = DEFAULT_ARCHIVE_LIMITS.maxArchiveBytes;
|
|
2846
|
+
try {
|
|
2847
|
+
const stat = fs.fstatSync(fd);
|
|
2848
|
+
if (!stat.isFile()) throw new ArchiveRefused("archive_path");
|
|
2849
|
+
if (stat.size > limit) throw new ArchiveRefused("archive_limit");
|
|
2850
|
+
const chunks = [];
|
|
2851
|
+
let total = 0;
|
|
2852
|
+
for (; ; ) {
|
|
2853
|
+
const chunk = new Uint8Array(Math.min(65536, limit + 1 - total));
|
|
2854
|
+
const count = fs.readSync(fd, chunk, 0, chunk.length, null);
|
|
2855
|
+
if (count === 0) break;
|
|
2856
|
+
if (total + count > limit) throw new ArchiveRefused("archive_limit");
|
|
2857
|
+
chunks.push(chunk.subarray(0, count));
|
|
2858
|
+
total += count;
|
|
2859
|
+
}
|
|
2860
|
+
const bytes = new Uint8Array(total);
|
|
2861
|
+
let at2 = 0;
|
|
2862
|
+
for (const chunk of chunks) {
|
|
2863
|
+
bytes.set(chunk, at2);
|
|
2864
|
+
at2 += chunk.length;
|
|
2865
|
+
}
|
|
2866
|
+
return bytes;
|
|
2867
|
+
} finally {
|
|
2868
|
+
fs.closeSync(fd);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2672
2872
|
// src/reader/ods/ods-formula.ts
|
|
2673
2873
|
function odsFormulaToA1(formula2) {
|
|
2674
2874
|
let grammar = "of";
|
|
@@ -3448,7 +3648,7 @@ function at(node, name) {
|
|
|
3448
3648
|
var MAX_ROWS = 1048576;
|
|
3449
3649
|
var MAX_COLUMNS = 16384;
|
|
3450
3650
|
var UNIX_EPOCH_SERIAL = 25569;
|
|
3451
|
-
var
|
|
3651
|
+
var decoder2 = new TextDecoder();
|
|
3452
3652
|
var OdsReader = class {
|
|
3453
3653
|
describe(bytes) {
|
|
3454
3654
|
return WorkbookSchema.fromWorkbook(this.readWorkbook(bytes));
|
|
@@ -3458,7 +3658,7 @@ var OdsReader = class {
|
|
|
3458
3658
|
}
|
|
3459
3659
|
/** Read an already-unzipped package (name -> bytes). */
|
|
3460
3660
|
readFiles(files) {
|
|
3461
|
-
const getText = (name) => files[name] ?
|
|
3661
|
+
const getText = (name) => files[name] ? decoder2.decode(files[name]) : null;
|
|
3462
3662
|
const content = getText("content.xml");
|
|
3463
3663
|
if (content === null) throw new Error("[holy-sheet] missing content.xml");
|
|
3464
3664
|
const contentDoc = parseXml(content, { namespaces: true });
|
|
@@ -3820,7 +4020,7 @@ function points(ser) {
|
|
|
3820
4020
|
byIndex.set(parseInt(idx, 10), color2);
|
|
3821
4021
|
}
|
|
3822
4022
|
}
|
|
3823
|
-
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([
|
|
4023
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index2, color2]) => ({ index: index2, color: color2 }));
|
|
3824
4024
|
}
|
|
3825
4025
|
function seriesColor(ser) {
|
|
3826
4026
|
const spPr = el(ser, "spPr");
|
|
@@ -4128,7 +4328,7 @@ function parseFrozen(xml) {
|
|
|
4128
4328
|
}
|
|
4129
4329
|
|
|
4130
4330
|
// src/reader/xlsx-reader.ts
|
|
4131
|
-
var
|
|
4331
|
+
var decoder3 = new TextDecoder();
|
|
4132
4332
|
var XlsxReader = class {
|
|
4133
4333
|
describe(bytes) {
|
|
4134
4334
|
return WorkbookSchema.fromWorkbook(this.readWorkbook(bytes));
|
|
@@ -4138,7 +4338,7 @@ var XlsxReader = class {
|
|
|
4138
4338
|
}
|
|
4139
4339
|
/** Read an already-unzipped package (name -> bytes), so a caller that sniffed it does not unzip twice. */
|
|
4140
4340
|
readFiles(files) {
|
|
4141
|
-
const getText = (name) => files[name] ?
|
|
4341
|
+
const getText = (name) => files[name] ? decoder3.decode(files[name]) : null;
|
|
4142
4342
|
const stylesXml = getText("xl/styles.xml");
|
|
4143
4343
|
const stylesIndex = stylesXml ? StylesParser.parse(stylesXml) : [];
|
|
4144
4344
|
const sharedStringsXml = getText("xl/sharedStrings.xml");
|
|
@@ -5008,11 +5208,11 @@ var SheetReducer = {
|
|
|
5008
5208
|
if (!isArr(get(op, "sheet"))) {
|
|
5009
5209
|
return schema;
|
|
5010
5210
|
}
|
|
5011
|
-
const
|
|
5012
|
-
if (
|
|
5211
|
+
const index2 = has(op, "index") ? phpInteger(op.index) : sheets.length;
|
|
5212
|
+
if (index2 === null) {
|
|
5013
5213
|
return schema;
|
|
5014
5214
|
}
|
|
5015
|
-
sheets.splice(Math.max(0, Math.min(sheets.length,
|
|
5215
|
+
sheets.splice(Math.max(0, Math.min(sheets.length, index2)), 0, op.sheet);
|
|
5016
5216
|
return { ...schema, sheets };
|
|
5017
5217
|
}
|
|
5018
5218
|
const at2 = find(sheets, phpString(get(op, "sheet") ?? ""));
|
|
@@ -5188,11 +5388,11 @@ function shiftColumns(sheet, at2, delta) {
|
|
|
5188
5388
|
});
|
|
5189
5389
|
if (isArr(get(sheet, "columnWidths"))) {
|
|
5190
5390
|
const widths = /* @__PURE__ */ new Map();
|
|
5191
|
-
for (const [
|
|
5192
|
-
if (!isIndexKey(
|
|
5391
|
+
for (const [index2, width] of entriesOf(sheet.columnWidths)) {
|
|
5392
|
+
if (!isIndexKey(index2)) {
|
|
5193
5393
|
continue;
|
|
5194
5394
|
}
|
|
5195
|
-
const i = phpInt2(
|
|
5395
|
+
const i = phpInt2(index2);
|
|
5196
5396
|
const number = i + 1;
|
|
5197
5397
|
if (number < at2) {
|
|
5198
5398
|
widths.set(i, width);
|
|
@@ -5201,8 +5401,8 @@ function shiftColumns(sheet, at2, delta) {
|
|
|
5201
5401
|
}
|
|
5202
5402
|
}
|
|
5203
5403
|
const sorted = {};
|
|
5204
|
-
for (const [
|
|
5205
|
-
sorted[
|
|
5404
|
+
for (const [index2, width] of [...widths.entries()].sort((x, y) => x[0] - y[0])) {
|
|
5405
|
+
sorted[index2] = width;
|
|
5206
5406
|
}
|
|
5207
5407
|
sheet = setOrUnset(sheet, "columnWidths", sorted, EMPTY);
|
|
5208
5408
|
}
|
|
@@ -5465,22 +5665,22 @@ function granular(a, b) {
|
|
|
5465
5665
|
ops.push({ type: "rename_sheet", sheet: name, name: renames.get(name) });
|
|
5466
5666
|
}
|
|
5467
5667
|
}
|
|
5468
|
-
namesB.forEach((name,
|
|
5668
|
+
namesB.forEach((name, index2) => {
|
|
5469
5669
|
if (unmatchedAdded.includes(name)) {
|
|
5470
|
-
ops.push({ type: "add_sheet", index, sheet: sheetsB[
|
|
5670
|
+
ops.push({ type: "add_sheet", index: index2, sheet: sheetsB[index2] });
|
|
5471
5671
|
}
|
|
5472
5672
|
});
|
|
5473
5673
|
let state = SheetReducer.applyAll(a, ops);
|
|
5474
|
-
namesB.forEach((name,
|
|
5674
|
+
namesB.forEach((name, index2) => {
|
|
5475
5675
|
const current = new Map(entriesOf(get(state, "sheets") ?? []).map(([key, sheet]) => [key, sheetName(sheet)]));
|
|
5476
|
-
if ((current.get(String(
|
|
5477
|
-
const op = { type: "move_sheet", sheet: name, toIndex:
|
|
5676
|
+
if ((current.get(String(index2)) ?? null) !== name) {
|
|
5677
|
+
const op = { type: "move_sheet", sheet: name, toIndex: index2 };
|
|
5478
5678
|
ops.push(op);
|
|
5479
5679
|
state = SheetReducer.apply(state, op);
|
|
5480
5680
|
}
|
|
5481
5681
|
});
|
|
5482
|
-
sheetsB.forEach((target,
|
|
5483
|
-
const current = state.sheets[
|
|
5682
|
+
sheetsB.forEach((target, index2) => {
|
|
5683
|
+
const current = state.sheets[index2];
|
|
5484
5684
|
if (SheetDiff.same(current, target)) {
|
|
5485
5685
|
return;
|
|
5486
5686
|
}
|
|
@@ -6131,7 +6331,7 @@ var holy_sheet_schema_default = {
|
|
|
6131
6331
|
};
|
|
6132
6332
|
|
|
6133
6333
|
// src/agent.ts
|
|
6134
|
-
var VERSION = "2.10.
|
|
6334
|
+
var VERSION = "2.10.2";
|
|
6135
6335
|
function toU8(input) {
|
|
6136
6336
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
6137
6337
|
}
|
|
@@ -6176,8 +6376,8 @@ var Agent = {
|
|
|
6176
6376
|
async describe(path) {
|
|
6177
6377
|
const fs = await import('fs');
|
|
6178
6378
|
if (!fs.existsSync(path)) return { error: "not_found", path };
|
|
6179
|
-
const bytes =
|
|
6180
|
-
return readSpreadsheet(
|
|
6379
|
+
const bytes = await readFileBounded(path);
|
|
6380
|
+
return readSpreadsheet(bytes, path);
|
|
6181
6381
|
},
|
|
6182
6382
|
/** Validate + conservative repairs in one call. */
|
|
6183
6383
|
validateAndRepair(schema) {
|
|
@@ -6295,6 +6495,7 @@ var HolySheet = class {
|
|
|
6295
6495
|
HolySheet.VERSION = VERSION;
|
|
6296
6496
|
|
|
6297
6497
|
exports.Agent = Agent;
|
|
6498
|
+
exports.ArchiveRefused = ArchiveRefused;
|
|
6298
6499
|
exports.ArrayBuilder = ArrayBuilder;
|
|
6299
6500
|
exports.CellAddress = CellAddress;
|
|
6300
6501
|
exports.CsvBuilder = CsvBuilder;
|