@particle-academy/holy-sheet 2.10.1 → 2.11.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/CHANGELOG.md +52 -0
- package/README.md +34 -0
- package/dist/chunk-OL6E7DE5.js +6828 -0
- package/dist/chunk-OL6E7DE5.js.map +1 -0
- package/dist/cli.cjs +6909 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +845 -267
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -5
- package/dist/index.d.ts +62 -5
- package/dist/index.js +9 -6255
- package/dist/index.js.map +1 -1
- package/package.json +5 -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
|
},
|
|
@@ -521,9 +521,10 @@ var ChartRange = class _ChartRange {
|
|
|
521
521
|
|
|
522
522
|
// src/workbook/workbook.ts
|
|
523
523
|
var Workbook = class {
|
|
524
|
-
constructor(sheets, meta = {}) {
|
|
524
|
+
constructor(sheets, meta = {}, sourceKind2 = "document") {
|
|
525
525
|
this.sheets = sheets;
|
|
526
526
|
this.meta = meta;
|
|
527
|
+
this.sourceKind = sourceKind2;
|
|
527
528
|
}
|
|
528
529
|
};
|
|
529
530
|
|
|
@@ -560,6 +561,31 @@ var DateConverter = {
|
|
|
560
561
|
};
|
|
561
562
|
|
|
562
563
|
// src/exceptions.ts
|
|
564
|
+
var ArchiveRefused = class _ArchiveRefused extends Error {
|
|
565
|
+
constructor(reason) {
|
|
566
|
+
super(`Source archive refused: ${reason}`);
|
|
567
|
+
this.reason = reason;
|
|
568
|
+
this.name = "ArchiveRefused";
|
|
569
|
+
Object.setPrototypeOf(this, _ArchiveRefused.prototype);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var TEMPLATE_MESSAGES = {
|
|
573
|
+
macro_enabled: "Macro-enabled templates (.xltm) are refused: re-save it as an .xltx in Excel.",
|
|
574
|
+
family_mismatch: "This package declares a template of a different kind than its contents.",
|
|
575
|
+
already_template: "This package is already a template.",
|
|
576
|
+
already_document: "This package is already a document.",
|
|
577
|
+
not_convertible: "This package has no single content-type declaration that can be rewritten.",
|
|
578
|
+
odf_mimetype: "An OpenDocument package must store its mimetype entry first and uncompressed.",
|
|
579
|
+
bad_target: 'The conversion target must be "document" or "template".'
|
|
580
|
+
};
|
|
581
|
+
var OfficeTemplateRefused = class _OfficeTemplateRefused extends Error {
|
|
582
|
+
constructor(reason) {
|
|
583
|
+
super(`Template refused (${reason}): ${TEMPLATE_MESSAGES[reason] ?? reason}`);
|
|
584
|
+
this.reason = reason;
|
|
585
|
+
this.name = "OfficeTemplateRefused";
|
|
586
|
+
Object.setPrototypeOf(this, _OfficeTemplateRefused.prototype);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
563
589
|
var SchemaException = class _SchemaException extends Error {
|
|
564
590
|
constructor(errors, message) {
|
|
565
591
|
super(message ?? summarize(errors));
|
|
@@ -800,23 +826,23 @@ var Normalizer = class {
|
|
|
800
826
|
for (const [colIdx, columnDef] of (sheet.columns ?? []).entries()) {
|
|
801
827
|
if (columnDef === null || typeof columnDef !== "object") continue;
|
|
802
828
|
if (columnDef.width === void 0) continue;
|
|
803
|
-
const
|
|
829
|
+
const index2 = ColumnWidths.index(colIdx);
|
|
804
830
|
const width = ColumnWidths.width(columnDef.width);
|
|
805
|
-
if (
|
|
806
|
-
out.set(
|
|
831
|
+
if (index2 === null || width === null) continue;
|
|
832
|
+
out.set(index2, width);
|
|
807
833
|
}
|
|
808
|
-
for (const [
|
|
809
|
-
out.set(
|
|
834
|
+
for (const [index2, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
|
|
835
|
+
out.set(index2, width);
|
|
810
836
|
}
|
|
811
837
|
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
812
838
|
}
|
|
813
839
|
normalizeColumnWidths(widths) {
|
|
814
840
|
const out = /* @__PURE__ */ new Map();
|
|
815
841
|
for (const [key, px] of Object.entries(widths)) {
|
|
816
|
-
const
|
|
842
|
+
const index2 = ColumnWidths.index(key);
|
|
817
843
|
const width = ColumnWidths.width(px);
|
|
818
|
-
if (
|
|
819
|
-
out.set(
|
|
844
|
+
if (index2 === null || width === null) continue;
|
|
845
|
+
out.set(index2, width);
|
|
820
846
|
}
|
|
821
847
|
return out;
|
|
822
848
|
}
|
|
@@ -928,14 +954,14 @@ function normalizePoints(points2, count) {
|
|
|
928
954
|
const byIndex = /* @__PURE__ */ new Map();
|
|
929
955
|
for (const p of Object.values(points2)) {
|
|
930
956
|
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
931
|
-
const
|
|
957
|
+
const index2 = rec ? rec.index : void 0;
|
|
932
958
|
const color2 = rec ? rec.color : void 0;
|
|
933
959
|
const m = typeof color2 === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(color2) : null;
|
|
934
|
-
if (typeof
|
|
935
|
-
byIndex.set(
|
|
960
|
+
if (typeof index2 === "number" && Number.isInteger(index2) && index2 >= 0 && index2 < count && m) {
|
|
961
|
+
byIndex.set(index2, m[1].toUpperCase());
|
|
936
962
|
}
|
|
937
963
|
}
|
|
938
|
-
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([
|
|
964
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index2, color2]) => ({ index: index2, color: color2 }));
|
|
939
965
|
}
|
|
940
966
|
var CHART_TYPES = ["column", "bar", "line", "pie"];
|
|
941
967
|
function normalizeCharts(charts, sheetName2, cells) {
|
|
@@ -1069,13 +1095,13 @@ var FormulaLinter = class {
|
|
|
1069
1095
|
return issues;
|
|
1070
1096
|
}
|
|
1071
1097
|
buildIndex(wb) {
|
|
1072
|
-
const
|
|
1098
|
+
const index2 = /* @__PURE__ */ new Map();
|
|
1073
1099
|
for (const sheet of wb.sheets) {
|
|
1074
1100
|
for (const [address, cell] of sheet.cells) {
|
|
1075
|
-
|
|
1101
|
+
index2.set(sheet.name + "!" + address, cell);
|
|
1076
1102
|
}
|
|
1077
1103
|
}
|
|
1078
|
-
return
|
|
1104
|
+
return index2;
|
|
1079
1105
|
}
|
|
1080
1106
|
isError(v) {
|
|
1081
1107
|
return typeof v === "string" && v.length > 0 && v[0] === "#";
|
|
@@ -1589,21 +1615,21 @@ var Repairer = class {
|
|
|
1589
1615
|
const widths = {};
|
|
1590
1616
|
let changed = false;
|
|
1591
1617
|
for (const [key, px] of Object.entries(sheet.columnWidths)) {
|
|
1592
|
-
let
|
|
1593
|
-
if (
|
|
1618
|
+
let index2 = ColumnWidths.index(key);
|
|
1619
|
+
if (index2 === null) {
|
|
1594
1620
|
const letters = ColumnWidths.fromLetters(key);
|
|
1595
1621
|
if (letters !== null) {
|
|
1596
|
-
|
|
1597
|
-
this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${
|
|
1622
|
+
index2 = letters;
|
|
1623
|
+
this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${index2}`);
|
|
1598
1624
|
changed = true;
|
|
1599
1625
|
}
|
|
1600
1626
|
}
|
|
1601
|
-
if (
|
|
1627
|
+
if (index2 === null || ColumnWidths.width(px) === null) {
|
|
1602
1628
|
this.repairs.push(`dropped '${path}.columnWidths.${key}' (not a column index and a width)`);
|
|
1603
1629
|
changed = true;
|
|
1604
1630
|
continue;
|
|
1605
1631
|
}
|
|
1606
|
-
widths[String(
|
|
1632
|
+
widths[String(index2)] = px;
|
|
1607
1633
|
}
|
|
1608
1634
|
if (changed) {
|
|
1609
1635
|
sheet.columnWidths = widths;
|
|
@@ -2027,14 +2053,14 @@ function validatePoints(points2, at2, values2) {
|
|
|
2027
2053
|
const errors = [];
|
|
2028
2054
|
points2.forEach((p, k) => {
|
|
2029
2055
|
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
2030
|
-
const
|
|
2056
|
+
const index2 = rec ? rec.index ?? null : null;
|
|
2031
2057
|
const color2 = rec ? rec.color ?? null : null;
|
|
2032
|
-
if (typeof
|
|
2058
|
+
if (typeof index2 !== "number" || !Number.isInteger(index2) || index2 < 0 || index2 >= count) {
|
|
2033
2059
|
errors.push(error(
|
|
2034
2060
|
`${at2}[${k}].index`,
|
|
2035
2061
|
"an index within values (0 to " + Math.max(0, count - 1) + ")",
|
|
2036
|
-
typeOf(
|
|
2037
|
-
|
|
2062
|
+
typeOf(index2),
|
|
2063
|
+
index2,
|
|
2038
2064
|
'The 0-based position of the point in its values range: "B5:B7" has points 0, 1 and 2.'
|
|
2039
2065
|
));
|
|
2040
2066
|
}
|
|
@@ -2429,8 +2455,17 @@ function buildTree(t, lengths, off, num) {
|
|
|
2429
2455
|
let i;
|
|
2430
2456
|
let sum = 0;
|
|
2431
2457
|
for (i = 0; i < 16; i++) t.table[i] = 0;
|
|
2432
|
-
for (i = 0; i < num; i++)
|
|
2458
|
+
for (i = 0; i < num; i++) {
|
|
2459
|
+
const length = lengths[off + i];
|
|
2460
|
+
if (length === void 0 || length > 15) throw new Error("holy-sheet: invalid DEFLATE tree");
|
|
2461
|
+
t.table[length]++;
|
|
2462
|
+
}
|
|
2433
2463
|
t.table[0] = 0;
|
|
2464
|
+
let slots = 1;
|
|
2465
|
+
for (i = 1; i <= 15; i++) {
|
|
2466
|
+
slots = slots * 2 - t.table[i];
|
|
2467
|
+
if (slots < 0) throw new Error("holy-sheet: oversubscribed DEFLATE tree");
|
|
2468
|
+
}
|
|
2434
2469
|
for (i = 0; i < 16; i++) {
|
|
2435
2470
|
OFFS[i] = sum;
|
|
2436
2471
|
sum += t.table[i];
|
|
@@ -2450,6 +2485,7 @@ var Reader = class {
|
|
|
2450
2485
|
};
|
|
2451
2486
|
function getBit(d) {
|
|
2452
2487
|
if (d.bitcount-- === 0) {
|
|
2488
|
+
if (d.index >= d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
|
|
2453
2489
|
d.tag = d.source[d.index++];
|
|
2454
2490
|
d.bitcount = 7;
|
|
2455
2491
|
}
|
|
@@ -2470,6 +2506,7 @@ function decodeSymbol(d, t) {
|
|
|
2470
2506
|
do {
|
|
2471
2507
|
cur = 2 * cur + getBit(d);
|
|
2472
2508
|
len++;
|
|
2509
|
+
if (len > 15) throw new Error("holy-sheet: invalid DEFLATE code");
|
|
2473
2510
|
sum += t.table[len];
|
|
2474
2511
|
cur -= t.table[len];
|
|
2475
2512
|
} while (cur >= 0);
|
|
@@ -2480,6 +2517,7 @@ function decodeTrees(d, lt, dt) {
|
|
|
2480
2517
|
const hlit = readBits(d, 5, 257);
|
|
2481
2518
|
const hdist = readBits(d, 5, 1);
|
|
2482
2519
|
const hclen = readBits(d, 4, 4);
|
|
2520
|
+
if (hlit > 286) throw new Error("holy-sheet: invalid DEFLATE literal count");
|
|
2483
2521
|
let i;
|
|
2484
2522
|
for (i = 0; i < 19; i++) lengths[i] = 0;
|
|
2485
2523
|
for (i = 0; i < hclen; i++) {
|
|
@@ -2492,32 +2530,44 @@ function decodeTrees(d, lt, dt) {
|
|
|
2492
2530
|
const sym = decodeSymbol(d, codeTree);
|
|
2493
2531
|
switch (sym) {
|
|
2494
2532
|
case 16: {
|
|
2533
|
+
if (num === 0) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2495
2534
|
const prev = lengths[num - 1];
|
|
2496
|
-
|
|
2535
|
+
const repeat = readBits(d, 2, 3);
|
|
2536
|
+
if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2537
|
+
for (let length = repeat; length; length--) lengths[num++] = prev;
|
|
2497
2538
|
break;
|
|
2498
2539
|
}
|
|
2499
|
-
case 17:
|
|
2500
|
-
|
|
2540
|
+
case 17: {
|
|
2541
|
+
const repeat = readBits(d, 3, 3);
|
|
2542
|
+
if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2543
|
+
for (let length = repeat; length; length--) lengths[num++] = 0;
|
|
2501
2544
|
break;
|
|
2502
|
-
|
|
2503
|
-
|
|
2545
|
+
}
|
|
2546
|
+
case 18: {
|
|
2547
|
+
const repeat = readBits(d, 7, 11);
|
|
2548
|
+
if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
|
|
2549
|
+
for (let length = repeat; length; length--) lengths[num++] = 0;
|
|
2504
2550
|
break;
|
|
2551
|
+
}
|
|
2505
2552
|
default:
|
|
2506
2553
|
lengths[num++] = sym;
|
|
2507
2554
|
break;
|
|
2508
2555
|
}
|
|
2509
2556
|
}
|
|
2557
|
+
if (lengths[256] === 0) throw new Error("holy-sheet: missing DEFLATE end code");
|
|
2510
2558
|
buildTree(lt, lengths, 0, hlit);
|
|
2511
2559
|
buildTree(dt, lengths, hlit, hdist);
|
|
2512
2560
|
}
|
|
2513
2561
|
var Out = class {
|
|
2514
|
-
constructor() {
|
|
2515
|
-
this.
|
|
2562
|
+
constructor(limit) {
|
|
2563
|
+
this.limit = limit;
|
|
2516
2564
|
this.len = 0;
|
|
2565
|
+
this.buf = new Uint8Array(Math.min(1024, limit));
|
|
2517
2566
|
}
|
|
2518
2567
|
push(b) {
|
|
2568
|
+
if (this.len >= this.limit) throw new Error("holy-sheet: DEFLATE output limit exceeded");
|
|
2519
2569
|
if (this.len >= this.buf.length) {
|
|
2520
|
-
const next = new Uint8Array(this.buf.length * 2);
|
|
2570
|
+
const next = new Uint8Array(Math.min(this.limit, Math.max(1, this.buf.length * 2)));
|
|
2521
2571
|
next.set(this.buf);
|
|
2522
2572
|
this.buf = next;
|
|
2523
2573
|
}
|
|
@@ -2532,9 +2582,12 @@ function inflateBlockData(d, out, lt, dt) {
|
|
|
2532
2582
|
out.push(sym);
|
|
2533
2583
|
} else {
|
|
2534
2584
|
const lengthSym = sym - 257;
|
|
2585
|
+
if (lengthSym < 0 || lengthSym >= LENGTH_BITS.length) throw new Error("holy-sheet: invalid DEFLATE length");
|
|
2535
2586
|
const length = readBits(d, LENGTH_BITS[lengthSym], LENGTH_BASE[lengthSym]);
|
|
2536
2587
|
const distSym = decodeSymbol(d, dt);
|
|
2588
|
+
if (distSym < 0 || distSym >= DIST_BITS.length) throw new Error("holy-sheet: invalid DEFLATE distance");
|
|
2537
2589
|
const dist = readBits(d, DIST_BITS[distSym], DIST_BASE[distSym]);
|
|
2590
|
+
if (dist > out.len) throw new Error("holy-sheet: DEFLATE distance before output");
|
|
2538
2591
|
const offs = out.len - dist;
|
|
2539
2592
|
for (let i = 0; i < length; i++) out.push(out.buf[offs + i]);
|
|
2540
2593
|
}
|
|
@@ -2542,13 +2595,20 @@ function inflateBlockData(d, out, lt, dt) {
|
|
|
2542
2595
|
}
|
|
2543
2596
|
function inflateUncompressedBlock(d, out) {
|
|
2544
2597
|
d.bitcount = 0;
|
|
2598
|
+
if (d.index + 4 > d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
|
|
2545
2599
|
let length = d.source[d.index + 1] * 256 + d.source[d.index];
|
|
2600
|
+
const complement = d.source[d.index + 3] * 256 + d.source[d.index + 2];
|
|
2601
|
+
if ((length ^ complement) !== 65535) throw new Error("holy-sheet: invalid DEFLATE stored length");
|
|
2546
2602
|
d.index += 4;
|
|
2603
|
+
if (d.index + length > d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
|
|
2547
2604
|
for (; length; length--) out.push(d.source[d.index++]);
|
|
2548
2605
|
}
|
|
2549
|
-
function inflateRaw(source) {
|
|
2606
|
+
function inflateRaw(source, maxOutputBytes = Number.POSITIVE_INFINITY) {
|
|
2607
|
+
if (maxOutputBytes !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 0)) {
|
|
2608
|
+
throw new Error("holy-sheet: invalid DEFLATE output limit");
|
|
2609
|
+
}
|
|
2550
2610
|
const d = new Reader(source);
|
|
2551
|
-
const out = new Out();
|
|
2611
|
+
const out = new Out(maxOutputBytes);
|
|
2552
2612
|
const lt = new Tree();
|
|
2553
2613
|
const dt = new Tree();
|
|
2554
2614
|
let bfinal;
|
|
@@ -2570,41 +2630,190 @@ function inflateRaw(source) {
|
|
|
2570
2630
|
return out.buf.subarray(0, out.len);
|
|
2571
2631
|
}
|
|
2572
2632
|
|
|
2573
|
-
// src/zip/
|
|
2574
|
-
var
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2633
|
+
// src/zip/source-archive.ts
|
|
2634
|
+
var DEFAULT_ARCHIVE_LIMITS = Object.freeze({
|
|
2635
|
+
maxArchiveBytes: 67108864,
|
|
2636
|
+
maxEntries: 16384,
|
|
2637
|
+
maxPartBytes: 67108864,
|
|
2638
|
+
maxExpandedBytes: 268435456,
|
|
2639
|
+
maxCompressionRatio: 1e3
|
|
2640
|
+
});
|
|
2641
|
+
var corrupt = () => {
|
|
2642
|
+
throw new ArchiveRefused("archive_corrupt");
|
|
2643
|
+
};
|
|
2644
|
+
var SourceArchive = class _SourceArchive {
|
|
2645
|
+
constructor(original, parts, records = [], archiveComment = new Uint8Array()) {
|
|
2646
|
+
this.original = original;
|
|
2647
|
+
this.parts = parts;
|
|
2648
|
+
this.records = records;
|
|
2649
|
+
this.archiveComment = archiveComment;
|
|
2650
|
+
}
|
|
2651
|
+
static open(input, options = {}) {
|
|
2652
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2653
|
+
if (!Object.prototype.hasOwnProperty.call(DEFAULT_ARCHIVE_LIMITS, key) || !Number.isSafeInteger(value) || value < 1) throw new ArchiveRefused("archive_limits");
|
|
2654
|
+
}
|
|
2655
|
+
const limits = { ...DEFAULT_ARCHIVE_LIMITS, ...options };
|
|
2656
|
+
if (input.length > limits.maxArchiveBytes) throw new ArchiveRefused("archive_limit");
|
|
2657
|
+
const bytes = input.slice();
|
|
2658
|
+
const [records, comment] = index(bytes, limits);
|
|
2659
|
+
const parts = /* @__PURE__ */ new Map();
|
|
2660
|
+
for (const record of records) {
|
|
2661
|
+
const compressed = bytes.subarray(record.start, record.start + record.compressed);
|
|
2662
|
+
let payload;
|
|
2663
|
+
try {
|
|
2664
|
+
payload = record.method === 0 ? compressed.slice() : inflateRaw(compressed, record.size + 1);
|
|
2665
|
+
} catch {
|
|
2666
|
+
throw new ArchiveRefused("archive_corrupt");
|
|
2667
|
+
}
|
|
2668
|
+
if (payload.length !== record.size || crc32(payload) !== record.crc) corrupt();
|
|
2669
|
+
parts.set(record.name, payload);
|
|
2582
2670
|
}
|
|
2671
|
+
const entries = records.map(({ name, size, crc, local, finish, central, centralLength, flags, method, localExtra }) => ({ name, size, crc, local, finish, central, centralLength, flags, method, localExtra }));
|
|
2672
|
+
return new _SourceArchive(bytes, parts, entries, comment);
|
|
2673
|
+
}
|
|
2674
|
+
names() {
|
|
2675
|
+
return [...this.parts.keys()];
|
|
2676
|
+
}
|
|
2677
|
+
read(name) {
|
|
2678
|
+
return this.parts.get(name)?.slice() ?? null;
|
|
2679
|
+
}
|
|
2680
|
+
toBytes() {
|
|
2681
|
+
return this.original.slice();
|
|
2682
|
+
}
|
|
2683
|
+
/** Verified member records in central-directory order. */
|
|
2684
|
+
entries() {
|
|
2685
|
+
return this.records.map((entry) => ({ ...entry }));
|
|
2583
2686
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2687
|
+
/** The archive comment, verbatim. */
|
|
2688
|
+
comment() {
|
|
2689
|
+
return this.archiveComment.slice();
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
function index(bytes, limits) {
|
|
2693
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2694
|
+
const bounds = (at3, size, end2 = bytes.length) => {
|
|
2695
|
+
if (at3 < 0 || size < 0 || at3 + size > end2 || at3 + size > bytes.length) corrupt();
|
|
2696
|
+
};
|
|
2697
|
+
const u16 = (at3) => {
|
|
2698
|
+
bounds(at3, 2);
|
|
2699
|
+
return view.getUint16(at3, true);
|
|
2700
|
+
};
|
|
2701
|
+
const u32 = (at3) => {
|
|
2702
|
+
bounds(at3, 4);
|
|
2703
|
+
return view.getUint32(at3, true);
|
|
2704
|
+
};
|
|
2705
|
+
let end = -1;
|
|
2706
|
+
for (let at3 = bytes.length - 22; at3 >= Math.max(0, bytes.length - 65557); at3--) {
|
|
2707
|
+
if (u32(at3) === 101010256 && at3 + 22 + u16(at3 + 20) === bytes.length && u32(at3 + 16) + u32(at3 + 12) === at3) {
|
|
2708
|
+
end = at3;
|
|
2709
|
+
break;
|
|
2710
|
+
}
|
|
2607
2711
|
}
|
|
2712
|
+
if (end === -1) corrupt();
|
|
2713
|
+
const count = u16(end + 10), directory = u32(end + 16);
|
|
2714
|
+
if (u16(end + 4) !== 0 || u16(end + 6) !== 0 || u16(end + 8) !== count || count === 65535) throw new ArchiveRefused("archive_unsupported");
|
|
2715
|
+
if (count > limits.maxEntries) throw new ArchiveRefused("archive_limit");
|
|
2716
|
+
if (end - directory < count * 46) corrupt();
|
|
2717
|
+
const records = [], seen = /* @__PURE__ */ new Set(), regions = [];
|
|
2718
|
+
let at2 = directory, expanded = 0;
|
|
2719
|
+
const extra = (start, length) => {
|
|
2720
|
+
const finish = start + length;
|
|
2721
|
+
while (start < finish) {
|
|
2722
|
+
bounds(start, 4, finish);
|
|
2723
|
+
const id = u16(start), size = u16(start + 2);
|
|
2724
|
+
bounds(start + 4, size, finish);
|
|
2725
|
+
if (id === 1 || id === 39169) throw new ArchiveRefused("archive_unsupported");
|
|
2726
|
+
start += 4 + size;
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
for (let i = 0; i < count; i++) {
|
|
2730
|
+
bounds(at2, 46, end);
|
|
2731
|
+
if (u32(at2) !== 33639248) corrupt();
|
|
2732
|
+
const flags = u16(at2 + 8), method = u16(at2 + 10), crc = u32(at2 + 16);
|
|
2733
|
+
const compressed = u32(at2 + 20), size = u32(at2 + 24), local = u32(at2 + 42);
|
|
2734
|
+
const nameLength = u16(at2 + 28), extraLength = u16(at2 + 30), commentLength = u16(at2 + 32);
|
|
2735
|
+
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");
|
|
2736
|
+
bounds(at2 + 46, nameLength + extraLength + commentLength, end);
|
|
2737
|
+
const name = partName(bytes.subarray(at2 + 46, at2 + 46 + nameLength), flags);
|
|
2738
|
+
if (seen.has(name)) throw new ArchiveRefused("archive_duplicate");
|
|
2739
|
+
seen.add(name);
|
|
2740
|
+
expanded += size;
|
|
2741
|
+
if (size > limits.maxPartBytes || expanded > limits.maxExpandedBytes || size > Math.max(1, compressed) * limits.maxCompressionRatio) throw new ArchiveRefused("archive_limit");
|
|
2742
|
+
if (method === 0 && compressed !== size) corrupt();
|
|
2743
|
+
extra(at2 + 46 + nameLength, extraLength);
|
|
2744
|
+
bounds(local, 30, directory);
|
|
2745
|
+
if (u32(local) !== 67324752 || u16(local + 4) !== u16(at2 + 6) || u16(local + 6) !== flags || u16(local + 8) !== method) corrupt();
|
|
2746
|
+
const localName2 = u16(local + 26), localExtra = u16(local + 28);
|
|
2747
|
+
const start = local + 30 + localName2 + localExtra;
|
|
2748
|
+
bounds(local + 30, localName2 + localExtra + compressed, directory);
|
|
2749
|
+
const rawName = bytes.subarray(local + 30, local + 30 + localName2);
|
|
2750
|
+
const centralName = bytes.subarray(at2 + 46, at2 + 46 + nameLength);
|
|
2751
|
+
if (rawName.length !== centralName.length || !rawName.every((byte, pos) => byte === centralName[pos])) corrupt();
|
|
2752
|
+
extra(local + 30 + localName2, localExtra);
|
|
2753
|
+
for (const [offset, expected] of [[14, crc], [18, compressed], [22, size]]) {
|
|
2754
|
+
const value = u32(local + offset);
|
|
2755
|
+
if (value !== expected && !((flags & 8) !== 0 && value === 0)) corrupt();
|
|
2756
|
+
}
|
|
2757
|
+
let finish = start + compressed;
|
|
2758
|
+
if ((flags & 8) !== 0) {
|
|
2759
|
+
let found = false;
|
|
2760
|
+
for (const prefix of [0, 4]) {
|
|
2761
|
+
if (finish + prefix + 12 <= directory && (prefix === 0 || u32(finish) === 134695760) && u32(finish + prefix) === crc && u32(finish + prefix + 4) === compressed && u32(finish + prefix + 8) === size) {
|
|
2762
|
+
finish += prefix + 12;
|
|
2763
|
+
found = true;
|
|
2764
|
+
break;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
if (!found) corrupt();
|
|
2768
|
+
}
|
|
2769
|
+
records.push({
|
|
2770
|
+
name,
|
|
2771
|
+
method,
|
|
2772
|
+
crc,
|
|
2773
|
+
size,
|
|
2774
|
+
compressed,
|
|
2775
|
+
start,
|
|
2776
|
+
local,
|
|
2777
|
+
finish,
|
|
2778
|
+
central: at2,
|
|
2779
|
+
centralLength: 46 + nameLength + extraLength + commentLength,
|
|
2780
|
+
flags,
|
|
2781
|
+
localExtra
|
|
2782
|
+
});
|
|
2783
|
+
regions.push([local, finish]);
|
|
2784
|
+
at2 += 46 + nameLength + extraLength + commentLength;
|
|
2785
|
+
}
|
|
2786
|
+
if (at2 !== end) corrupt();
|
|
2787
|
+
regions.sort((a, b) => a[0] - b[0]);
|
|
2788
|
+
let previous = 0;
|
|
2789
|
+
for (const [start, finish] of regions) {
|
|
2790
|
+
if (start < previous) corrupt();
|
|
2791
|
+
previous = finish;
|
|
2792
|
+
}
|
|
2793
|
+
return [records, bytes.slice(end + 22, end + 22 + u16(end + 20))];
|
|
2794
|
+
}
|
|
2795
|
+
function partName(bytes, flags) {
|
|
2796
|
+
let name;
|
|
2797
|
+
try {
|
|
2798
|
+
name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
2799
|
+
} catch {
|
|
2800
|
+
throw new ArchiveRefused("archive_path");
|
|
2801
|
+
}
|
|
2802
|
+
if (name === "" || name.startsWith("/") || name.includes(String.fromCharCode(92)) || name.includes(":")) throw new ArchiveRefused("archive_path");
|
|
2803
|
+
for (const byte of bytes) {
|
|
2804
|
+
if (byte < 32 || byte === 127) throw new ArchiveRefused("archive_path");
|
|
2805
|
+
if (byte > 127 && (flags & 2048) === 0) throw new ArchiveRefused("archive_unsupported");
|
|
2806
|
+
}
|
|
2807
|
+
const parts = (name.endsWith("/") ? name.slice(0, -1) : name).split("/");
|
|
2808
|
+
if (parts.some((part2) => part2 === "" || part2 === "." || part2 === "..")) throw new ArchiveRefused("archive_path");
|
|
2809
|
+
return name;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// src/zip/zip-reader.ts
|
|
2813
|
+
function unzipSync(data, limits = {}) {
|
|
2814
|
+
const source = SourceArchive.open(data, limits);
|
|
2815
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2816
|
+
for (const name of source.names()) result[name] = source.read(name);
|
|
2608
2817
|
return result;
|
|
2609
2818
|
}
|
|
2610
2819
|
|
|
@@ -2652,23 +2861,522 @@ var ODS_MIMETYPES = /* @__PURE__ */ new Set([
|
|
|
2652
2861
|
"application/vnd.oasis.opendocument.spreadsheet",
|
|
2653
2862
|
"application/vnd.oasis.opendocument.spreadsheet-template"
|
|
2654
2863
|
]);
|
|
2655
|
-
var
|
|
2864
|
+
var decoder = new TextDecoder();
|
|
2656
2865
|
var FormatSniffer = {
|
|
2657
2866
|
sniff(bytes, path = null) {
|
|
2658
2867
|
let files;
|
|
2659
2868
|
try {
|
|
2660
2869
|
files = unzipSync(bytes);
|
|
2661
2870
|
} catch (e) {
|
|
2871
|
+
if (e instanceof ArchiveRefused && (e.reason !== "archive_corrupt" || bytes[0] === 80 && bytes[1] === 75)) throw e;
|
|
2662
2872
|
if (e instanceof Error && e.message.includes("unsupported zip method")) throw e;
|
|
2663
2873
|
throw UnsupportedFormatException.notAZip(path);
|
|
2664
2874
|
}
|
|
2665
|
-
const mimetype = files["mimetype"] ? phpTrim(
|
|
2875
|
+
const mimetype = files["mimetype"] ? phpTrim(decoder.decode(files["mimetype"])) : null;
|
|
2666
2876
|
if (mimetype !== null && ODS_MIMETYPES.has(mimetype)) return { format: "ods", files };
|
|
2667
2877
|
if (files["xl/workbook.xml"]) return { format: "xlsx", files };
|
|
2668
2878
|
throw UnsupportedFormatException.unknownPackage(path, mimetype);
|
|
2669
2879
|
}
|
|
2670
2880
|
};
|
|
2671
2881
|
|
|
2882
|
+
// src/reader/xml.ts
|
|
2883
|
+
var XML_NS = "http://www.w3.org/XML/1998/namespace";
|
|
2884
|
+
function localName(name) {
|
|
2885
|
+
const idx = name.indexOf(":");
|
|
2886
|
+
return idx >= 0 ? name.slice(idx + 1) : name;
|
|
2887
|
+
}
|
|
2888
|
+
function unescapeXml(s) {
|
|
2889
|
+
if (s.indexOf("&") < 0) return s;
|
|
2890
|
+
return s.replace(/&(#x?[0-9a-fA-F]+|\w+);/g, (m, ent) => {
|
|
2891
|
+
switch (ent) {
|
|
2892
|
+
case "amp":
|
|
2893
|
+
return "&";
|
|
2894
|
+
case "lt":
|
|
2895
|
+
return "<";
|
|
2896
|
+
case "gt":
|
|
2897
|
+
return ">";
|
|
2898
|
+
case "quot":
|
|
2899
|
+
return '"';
|
|
2900
|
+
case "apos":
|
|
2901
|
+
return "'";
|
|
2902
|
+
default:
|
|
2903
|
+
if (ent[0] === "#") {
|
|
2904
|
+
const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
|
|
2905
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
2906
|
+
}
|
|
2907
|
+
return m;
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
function findTagEnd(src, from) {
|
|
2912
|
+
let quote2 = "";
|
|
2913
|
+
for (let i = from; i < src.length; i++) {
|
|
2914
|
+
const ch = src[i];
|
|
2915
|
+
if (quote2) {
|
|
2916
|
+
if (ch === quote2) quote2 = "";
|
|
2917
|
+
} else if (ch === '"' || ch === "'") {
|
|
2918
|
+
quote2 = ch;
|
|
2919
|
+
} else if (ch === ">") {
|
|
2920
|
+
return i;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return src.length;
|
|
2924
|
+
}
|
|
2925
|
+
var ATTR_RE = /([^\s=/]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
2926
|
+
function parseTag(inner) {
|
|
2927
|
+
const trimmed = inner.trim();
|
|
2928
|
+
let i = 0;
|
|
2929
|
+
while (i < trimmed.length && !/\s/.test(trimmed[i])) i++;
|
|
2930
|
+
const name = trimmed.slice(0, i);
|
|
2931
|
+
const attrs = {};
|
|
2932
|
+
const raw = [];
|
|
2933
|
+
const rest = trimmed.slice(i);
|
|
2934
|
+
for (const m of rest.matchAll(ATTR_RE)) {
|
|
2935
|
+
const rawValue = m[3] ?? m[4] ?? "";
|
|
2936
|
+
attrs[localName(m[1])] = unescapeXml(rawValue);
|
|
2937
|
+
raw.push([m[1], rawValue]);
|
|
2938
|
+
}
|
|
2939
|
+
return { name, attrs, raw };
|
|
2940
|
+
}
|
|
2941
|
+
function parseXml(src, options = {}) {
|
|
2942
|
+
const namespaces = options.namespaces === true;
|
|
2943
|
+
let i = 0;
|
|
2944
|
+
const n = src.length;
|
|
2945
|
+
const root = { name: "#root", attrs: {}, children: [], text: "" };
|
|
2946
|
+
if (namespaces) root.content = [];
|
|
2947
|
+
const stack = [root];
|
|
2948
|
+
const scopes = [{ xml: XML_NS }];
|
|
2949
|
+
const appendText = (text2) => {
|
|
2950
|
+
const top = stack[stack.length - 1];
|
|
2951
|
+
top.text += text2;
|
|
2952
|
+
if (namespaces && text2 !== "") top.content.push(text2);
|
|
2953
|
+
};
|
|
2954
|
+
while (i < n) {
|
|
2955
|
+
if (src[i] === "<") {
|
|
2956
|
+
if (src.startsWith("<!--", i)) {
|
|
2957
|
+
const end2 = src.indexOf("-->", i);
|
|
2958
|
+
i = end2 < 0 ? n : end2 + 3;
|
|
2959
|
+
continue;
|
|
2960
|
+
}
|
|
2961
|
+
if (src.startsWith("<![CDATA[", i)) {
|
|
2962
|
+
const end2 = src.indexOf("]]>", i);
|
|
2963
|
+
appendText(src.slice(i + 9, end2 < 0 ? n : end2));
|
|
2964
|
+
i = end2 < 0 ? n : end2 + 3;
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
if (src.startsWith("<?", i)) {
|
|
2968
|
+
const end2 = src.indexOf("?>", i);
|
|
2969
|
+
i = end2 < 0 ? n : end2 + 2;
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
if (src.startsWith("<!", i)) {
|
|
2973
|
+
const end2 = src.indexOf(">", i);
|
|
2974
|
+
i = end2 < 0 ? n : end2 + 1;
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
if (src[i + 1] === "/") {
|
|
2978
|
+
const end2 = src.indexOf(">", i);
|
|
2979
|
+
if (stack.length > 1) {
|
|
2980
|
+
stack.pop();
|
|
2981
|
+
if (namespaces) scopes.pop();
|
|
2982
|
+
}
|
|
2983
|
+
i = end2 < 0 ? n : end2 + 1;
|
|
2984
|
+
continue;
|
|
2985
|
+
}
|
|
2986
|
+
const end = findTagEnd(src, i + 1);
|
|
2987
|
+
const tagContent = src.slice(i + 1, end);
|
|
2988
|
+
const selfClosing = tagContent.endsWith("/");
|
|
2989
|
+
const { name, attrs, raw } = parseTag(selfClosing ? tagContent.slice(0, -1) : tagContent);
|
|
2990
|
+
const node = { name: localName(name), attrs, children: [], text: "" };
|
|
2991
|
+
const parent = stack[stack.length - 1];
|
|
2992
|
+
parent.children.push(node);
|
|
2993
|
+
let scope;
|
|
2994
|
+
if (namespaces) {
|
|
2995
|
+
scope = resolveNamespaces(node, name, raw, scopes[scopes.length - 1]);
|
|
2996
|
+
parent.content.push(node);
|
|
2997
|
+
}
|
|
2998
|
+
if (!selfClosing) {
|
|
2999
|
+
stack.push(node);
|
|
3000
|
+
if (scope) scopes.push(scope);
|
|
3001
|
+
}
|
|
3002
|
+
i = end + 1;
|
|
3003
|
+
} else {
|
|
3004
|
+
const next = src.indexOf("<", i);
|
|
3005
|
+
const stop = next < 0 ? n : next;
|
|
3006
|
+
appendText(unescapeXml(src.slice(i, stop)));
|
|
3007
|
+
i = stop;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return root.children[0] ?? null;
|
|
3011
|
+
}
|
|
3012
|
+
function resolveNamespaces(node, qname, raw, parentScope) {
|
|
3013
|
+
let scope = parentScope;
|
|
3014
|
+
for (const [key, value] of raw) {
|
|
3015
|
+
if (key === "xmlns" || key.startsWith("xmlns:")) {
|
|
3016
|
+
if (scope === parentScope) scope = { ...parentScope };
|
|
3017
|
+
scope[key === "xmlns" ? "" : key.slice(6)] = unescapeXml(value);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
const colon = qname.indexOf(":");
|
|
3021
|
+
node.ns = scope[colon >= 0 ? qname.slice(0, colon) : ""] ?? "";
|
|
3022
|
+
node.content = [];
|
|
3023
|
+
const nsAttrs = {};
|
|
3024
|
+
for (const [key, value] of raw) {
|
|
3025
|
+
if (key === "xmlns" || key.startsWith("xmlns:")) continue;
|
|
3026
|
+
const idx = key.indexOf(":");
|
|
3027
|
+
const normalised = unescapeXml(value.replace(/[\t\n\r]/g, " "));
|
|
3028
|
+
if (idx < 0) {
|
|
3029
|
+
nsAttrs[key] = normalised;
|
|
3030
|
+
} else {
|
|
3031
|
+
nsAttrs[`{${scope[key.slice(0, idx)] ?? ""}}${key.slice(idx + 1)}`] = normalised;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
node.nsAttrs = nsAttrs;
|
|
3035
|
+
return scope;
|
|
3036
|
+
}
|
|
3037
|
+
function el(node, name) {
|
|
3038
|
+
return node?.children.find((c) => c.name === name);
|
|
3039
|
+
}
|
|
3040
|
+
function els(node, name) {
|
|
3041
|
+
return node ? node.children.filter((c) => c.name === name) : [];
|
|
3042
|
+
}
|
|
3043
|
+
function at(node, name) {
|
|
3044
|
+
return node?.attrs[name];
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// src/zip/office-template.ts
|
|
3048
|
+
var DOCUMENT = "document";
|
|
3049
|
+
var TEMPLATE = "template";
|
|
3050
|
+
var FAMILY = "spreadsheet";
|
|
3051
|
+
var VBA = "application/vnd.ms-office.vbaProject";
|
|
3052
|
+
var FAMILIES = {
|
|
3053
|
+
word: {
|
|
3054
|
+
part: "/word/document.xml",
|
|
3055
|
+
document: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
|
|
3056
|
+
template: "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml",
|
|
3057
|
+
macroDocument: "application/vnd.ms-word.document.macroEnabled.main+xml",
|
|
3058
|
+
macroTemplate: "application/vnd.ms-word.template.macroEnabledTemplate.main+xml",
|
|
3059
|
+
odfDocument: "application/vnd.oasis.opendocument.text",
|
|
3060
|
+
odfTemplate: "application/vnd.oasis.opendocument.text-template",
|
|
3061
|
+
odfBody: "text"
|
|
3062
|
+
},
|
|
3063
|
+
presentation: {
|
|
3064
|
+
part: "/ppt/presentation.xml",
|
|
3065
|
+
document: "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml",
|
|
3066
|
+
template: "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml",
|
|
3067
|
+
macroDocument: "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml",
|
|
3068
|
+
macroTemplate: "application/vnd.ms-powerpoint.template.macroEnabled.main+xml",
|
|
3069
|
+
odfDocument: "application/vnd.oasis.opendocument.presentation",
|
|
3070
|
+
odfTemplate: "application/vnd.oasis.opendocument.presentation-template",
|
|
3071
|
+
odfBody: "presentation"
|
|
3072
|
+
},
|
|
3073
|
+
spreadsheet: {
|
|
3074
|
+
part: "/xl/workbook.xml",
|
|
3075
|
+
document: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
3076
|
+
template: "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml",
|
|
3077
|
+
macroDocument: "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
|
|
3078
|
+
macroTemplate: "application/vnd.ms-excel.template.macroEnabled.main+xml",
|
|
3079
|
+
odfDocument: "application/vnd.oasis.opendocument.spreadsheet",
|
|
3080
|
+
odfTemplate: "application/vnd.oasis.opendocument.spreadsheet-template",
|
|
3081
|
+
odfBody: "spreadsheet"
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
var OWN = FAMILIES[FAMILY];
|
|
3085
|
+
var UTF8 = new TextDecoder("utf-8");
|
|
3086
|
+
function filesView(files) {
|
|
3087
|
+
return {
|
|
3088
|
+
read: (name) => Object.prototype.hasOwnProperty.call(files, name) ? files[name] : null,
|
|
3089
|
+
names: () => Object.keys(files)
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
function sourceKind(zip) {
|
|
3093
|
+
const types = zip.read("[Content_Types].xml");
|
|
3094
|
+
if (types !== null) return ooxmlKind(zip, types);
|
|
3095
|
+
const mimetype = zip.read("mimetype");
|
|
3096
|
+
if (mimetype !== null) return odfKind(zip, latin1(mimetype));
|
|
3097
|
+
return DOCUMENT;
|
|
3098
|
+
}
|
|
3099
|
+
function convert(bytes, to = null) {
|
|
3100
|
+
if (to !== null && to !== DOCUMENT && to !== TEMPLATE) throw new OfficeTemplateRefused("bad_target");
|
|
3101
|
+
const zip = SourceArchive.open(new Uint8Array(bytes));
|
|
3102
|
+
const kind = sourceKind(zip);
|
|
3103
|
+
const target = to ?? (kind === TEMPLATE ? DOCUMENT : TEMPLATE);
|
|
3104
|
+
if (target === kind) throw new OfficeTemplateRefused(`already_${kind}`);
|
|
3105
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3106
|
+
const types = zip.read("[Content_Types].xml");
|
|
3107
|
+
const mimetypeBytes = zip.read("mimetype");
|
|
3108
|
+
if (types !== null) {
|
|
3109
|
+
const declared = declarations(types);
|
|
3110
|
+
if (declared === null) throw new OfficeTemplateRefused("not_convertible");
|
|
3111
|
+
if (hasVba(zip, declared) || typesOf(declared, OWN.part).includes(OWN.macroDocument)) {
|
|
3112
|
+
throw new OfficeTemplateRefused("macro_enabled");
|
|
3113
|
+
}
|
|
3114
|
+
const [from, value] = target === TEMPLATE ? [OWN.document, OWN.template] : [OWN.template, OWN.document];
|
|
3115
|
+
replacements.set("[Content_Types].xml", fromLatin1(rewriteOverride(latin1(types), OWN.part, from, value)));
|
|
3116
|
+
} else if (mimetypeBytes !== null) {
|
|
3117
|
+
const entries = zip.entries();
|
|
3118
|
+
const first2 = entries[0];
|
|
3119
|
+
const byLocal = [...entries].sort((a, b) => a.local - b.local);
|
|
3120
|
+
if (first2 === void 0 || first2.name !== "mimetype" || byLocal[0].name !== "mimetype" || first2.method !== 0 || first2.localExtra !== 0) {
|
|
3121
|
+
throw new OfficeTemplateRefused("odf_mimetype");
|
|
3122
|
+
}
|
|
3123
|
+
const [from, value] = target === TEMPLATE ? [OWN.odfDocument, OWN.odfTemplate] : [OWN.odfTemplate, OWN.odfDocument];
|
|
3124
|
+
if (latin1(mimetypeBytes) !== from) throw new OfficeTemplateRefused("not_convertible");
|
|
3125
|
+
replacements.set("mimetype", fromLatin1(value));
|
|
3126
|
+
const manifest = zip.read("META-INF/manifest.xml");
|
|
3127
|
+
if (manifest !== null) {
|
|
3128
|
+
const rewritten = rewriteManifestRoot(latin1(manifest), from, value);
|
|
3129
|
+
if (rewritten !== null) replacements.set("META-INF/manifest.xml", fromLatin1(rewritten));
|
|
3130
|
+
}
|
|
3131
|
+
} else {
|
|
3132
|
+
throw new OfficeTemplateRefused("not_convertible");
|
|
3133
|
+
}
|
|
3134
|
+
const out = rebuild(zip, replacements);
|
|
3135
|
+
if (sourceKind(SourceArchive.open(out)) !== target) {
|
|
3136
|
+
throw new Error("Template conversion did not produce the requested kind.");
|
|
3137
|
+
}
|
|
3138
|
+
return out;
|
|
3139
|
+
}
|
|
3140
|
+
function ooxmlKind(zip, types) {
|
|
3141
|
+
const declared = declarations(types);
|
|
3142
|
+
if (declared === null) return DOCUMENT;
|
|
3143
|
+
let kind = DOCUMENT;
|
|
3144
|
+
for (const [part2, type] of declared) {
|
|
3145
|
+
for (const [name, family] of Object.entries(FAMILIES)) {
|
|
3146
|
+
if (type !== family.template && type !== family.macroTemplate) continue;
|
|
3147
|
+
if (name !== FAMILY || part2 !== asciiLower2(OWN.part)) throw new OfficeTemplateRefused("family_mismatch");
|
|
3148
|
+
if (type === family.macroTemplate) throw new OfficeTemplateRefused("macro_enabled");
|
|
3149
|
+
kind = TEMPLATE;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
if (kind === TEMPLATE && hasVba(zip, declared)) throw new OfficeTemplateRefused("macro_enabled");
|
|
3153
|
+
return kind;
|
|
3154
|
+
}
|
|
3155
|
+
function odfKind(zip, mimetype) {
|
|
3156
|
+
if (mimetype !== OWN.odfTemplate) {
|
|
3157
|
+
if (mimetype.startsWith("application/vnd.oasis.opendocument.") && mimetype.endsWith("-template")) {
|
|
3158
|
+
throw new OfficeTemplateRefused("family_mismatch");
|
|
3159
|
+
}
|
|
3160
|
+
return DOCUMENT;
|
|
3161
|
+
}
|
|
3162
|
+
const content = dom(zip.read("content.xml"));
|
|
3163
|
+
const body = content !== null ? firstByLocalName(content, "body") : null;
|
|
3164
|
+
const first2 = body?.children[0] ?? null;
|
|
3165
|
+
if (first2 === null || first2.name !== OWN.odfBody) throw new OfficeTemplateRefused("family_mismatch");
|
|
3166
|
+
const root = manifestRootType(dom(zip.read("META-INF/manifest.xml")));
|
|
3167
|
+
if (root !== null && root !== OWN.odfTemplate && root !== OWN.odfDocument) {
|
|
3168
|
+
throw new OfficeTemplateRefused("family_mismatch");
|
|
3169
|
+
}
|
|
3170
|
+
return TEMPLATE;
|
|
3171
|
+
}
|
|
3172
|
+
function declarations(types) {
|
|
3173
|
+
const root = dom(types);
|
|
3174
|
+
if (root === null) return null;
|
|
3175
|
+
const out = [];
|
|
3176
|
+
for (const el2 of elements(root)) {
|
|
3177
|
+
if (el2.name === "Override") out.push([asciiLower2(el2.attrs["PartName"] ?? ""), el2.attrs["ContentType"] ?? ""]);
|
|
3178
|
+
else if (el2.name === "Default") out.push([`*.${asciiLower2(el2.attrs["Extension"] ?? "")}`, el2.attrs["ContentType"] ?? ""]);
|
|
3179
|
+
}
|
|
3180
|
+
return out;
|
|
3181
|
+
}
|
|
3182
|
+
function typesOf(declared, part2) {
|
|
3183
|
+
return declared.filter(([name]) => name === asciiLower2(part2)).map(([, type]) => type);
|
|
3184
|
+
}
|
|
3185
|
+
function hasVba(zip, declared) {
|
|
3186
|
+
if (declared.some(([, type]) => asciiLower2(type) === asciiLower2(VBA))) return true;
|
|
3187
|
+
return zip.names().some((name) => /(^|\/)vbaProject\.bin$/i.test(name));
|
|
3188
|
+
}
|
|
3189
|
+
function manifestRootType(root) {
|
|
3190
|
+
if (root === null) return null;
|
|
3191
|
+
for (const el2 of elements(root)) {
|
|
3192
|
+
if (el2.name === "file-entry" && el2.attrs["full-path"] === "/") return el2.attrs["media-type"] ?? null;
|
|
3193
|
+
}
|
|
3194
|
+
return null;
|
|
3195
|
+
}
|
|
3196
|
+
function rewriteOverride(xml, part2, from, to) {
|
|
3197
|
+
const hits = [];
|
|
3198
|
+
for (const tag of xml.matchAll(/<(?:[A-Za-z_][\w.\-]*:)?Override\b[^>]*>/g)) {
|
|
3199
|
+
const name = /\sPartName\s*=\s*(["'])(.*?)\1/s.exec(tag[0]);
|
|
3200
|
+
if (name === null || asciiLower2(name[2]) !== asciiLower2(part2)) continue;
|
|
3201
|
+
const type = /\sContentType\s*=\s*(["'])(.*?)\1/s.exec(tag[0]);
|
|
3202
|
+
if (type !== null && type[2] === from) hits.push(tag.index + type.index + type[0].indexOf(type[1]) + 1);
|
|
3203
|
+
}
|
|
3204
|
+
if (hits.length !== 1) throw new OfficeTemplateRefused("not_convertible");
|
|
3205
|
+
const out = xml.slice(0, hits[0]) + to + xml.slice(hits[0] + from.length);
|
|
3206
|
+
const declared = declarations(out);
|
|
3207
|
+
const after = declared === null ? [] : typesOf(declared, part2);
|
|
3208
|
+
if (after.length !== 1 || after[0] !== to) throw new OfficeTemplateRefused("not_convertible");
|
|
3209
|
+
return out;
|
|
3210
|
+
}
|
|
3211
|
+
function rewriteManifestRoot(xml, from, to) {
|
|
3212
|
+
const current = manifestRootType(dom(xml));
|
|
3213
|
+
if (current === null) return null;
|
|
3214
|
+
if (current !== from) throw new OfficeTemplateRefused("not_convertible");
|
|
3215
|
+
const hits = [];
|
|
3216
|
+
for (const tag of xml.matchAll(/<(?:[A-Za-z_][\w.\-]*:)?file-entry\b[^>]*>/g)) {
|
|
3217
|
+
if (!/\s(?:[A-Za-z_][\w.\-]*:)?full-path\s*=\s*(["'])\/\1/.test(tag[0])) continue;
|
|
3218
|
+
const type = /\s(?:[A-Za-z_][\w.\-]*:)?media-type\s*=\s*(["'])(.*?)\1/s.exec(tag[0]);
|
|
3219
|
+
if (type !== null && type[2] === from) hits.push(tag.index + type.index + type[0].indexOf(type[1]) + 1);
|
|
3220
|
+
}
|
|
3221
|
+
if (hits.length !== 1) throw new OfficeTemplateRefused("not_convertible");
|
|
3222
|
+
const out = xml.slice(0, hits[0]) + to + xml.slice(hits[0] + from.length);
|
|
3223
|
+
if (manifestRootType(dom(out)) !== to) throw new OfficeTemplateRefused("not_convertible");
|
|
3224
|
+
return out;
|
|
3225
|
+
}
|
|
3226
|
+
function rebuild(zip, replacements) {
|
|
3227
|
+
const original = zip.toBytes();
|
|
3228
|
+
const view = new DataView(original.buffer, original.byteOffset, original.byteLength);
|
|
3229
|
+
const u16 = (at2) => view.getUint16(at2, true);
|
|
3230
|
+
const u32 = (at2) => view.getUint32(at2, true);
|
|
3231
|
+
const entries = zip.entries();
|
|
3232
|
+
const byLocal = [...entries].sort((a, b) => a.local - b.local);
|
|
3233
|
+
const nameBytes = (entry) => original.subarray(entry.central + 46, entry.central + 46 + u16(entry.central + 28));
|
|
3234
|
+
const chunks = [];
|
|
3235
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
3236
|
+
let length = 0;
|
|
3237
|
+
const push = (chunk) => {
|
|
3238
|
+
chunks.push(chunk);
|
|
3239
|
+
length += chunk.length;
|
|
3240
|
+
};
|
|
3241
|
+
for (const entry of byLocal) {
|
|
3242
|
+
offsets.set(entry.name, length);
|
|
3243
|
+
const data = replacements.get(entry.name);
|
|
3244
|
+
if (data === void 0) {
|
|
3245
|
+
push(original.subarray(entry.local, entry.finish));
|
|
3246
|
+
continue;
|
|
3247
|
+
}
|
|
3248
|
+
const name = nameBytes(entry);
|
|
3249
|
+
const header = new Uint8Array(30 + name.length + data.length);
|
|
3250
|
+
const hv = new DataView(header.buffer);
|
|
3251
|
+
hv.setUint32(0, 67324752, true);
|
|
3252
|
+
hv.setUint16(4, 10, true);
|
|
3253
|
+
hv.setUint16(6, entry.flags & 2048, true);
|
|
3254
|
+
hv.setUint16(8, 0, true);
|
|
3255
|
+
hv.setUint16(10, u16(entry.local + 10), true);
|
|
3256
|
+
hv.setUint16(12, u16(entry.local + 12), true);
|
|
3257
|
+
hv.setUint32(14, crc32(data) >>> 0, true);
|
|
3258
|
+
hv.setUint32(18, data.length, true);
|
|
3259
|
+
hv.setUint32(22, data.length, true);
|
|
3260
|
+
hv.setUint16(26, name.length, true);
|
|
3261
|
+
hv.setUint16(28, 0, true);
|
|
3262
|
+
header.set(name, 30);
|
|
3263
|
+
header.set(data, 30 + name.length);
|
|
3264
|
+
push(header);
|
|
3265
|
+
}
|
|
3266
|
+
const localsLength = length;
|
|
3267
|
+
for (const entry of entries) {
|
|
3268
|
+
const data = replacements.get(entry.name);
|
|
3269
|
+
if (data === void 0) {
|
|
3270
|
+
const record2 = new Uint8Array(entry.centralLength);
|
|
3271
|
+
record2.set(original.subarray(entry.central, entry.central + entry.centralLength));
|
|
3272
|
+
new DataView(record2.buffer, record2.byteOffset, record2.byteLength).setUint32(42, offsets.get(entry.name), true);
|
|
3273
|
+
push(record2);
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
const name = nameBytes(entry);
|
|
3277
|
+
const at2 = entry.central;
|
|
3278
|
+
const record = new Uint8Array(46 + name.length);
|
|
3279
|
+
const rv = new DataView(record.buffer);
|
|
3280
|
+
rv.setUint32(0, 33639248, true);
|
|
3281
|
+
rv.setUint16(4, u16(at2 + 4), true);
|
|
3282
|
+
rv.setUint16(6, 10, true);
|
|
3283
|
+
rv.setUint16(8, entry.flags & 2048, true);
|
|
3284
|
+
rv.setUint16(10, 0, true);
|
|
3285
|
+
rv.setUint16(12, u16(at2 + 12), true);
|
|
3286
|
+
rv.setUint16(14, u16(at2 + 14), true);
|
|
3287
|
+
rv.setUint32(16, crc32(data) >>> 0, true);
|
|
3288
|
+
rv.setUint32(20, data.length, true);
|
|
3289
|
+
rv.setUint32(24, data.length, true);
|
|
3290
|
+
rv.setUint16(28, name.length, true);
|
|
3291
|
+
rv.setUint16(36, u16(at2 + 36), true);
|
|
3292
|
+
rv.setUint32(38, u32(at2 + 38), true);
|
|
3293
|
+
rv.setUint32(42, offsets.get(entry.name), true);
|
|
3294
|
+
record.set(name, 46);
|
|
3295
|
+
push(record);
|
|
3296
|
+
}
|
|
3297
|
+
const comment = zip.comment();
|
|
3298
|
+
const end = new Uint8Array(22 + comment.length);
|
|
3299
|
+
const ev = new DataView(end.buffer);
|
|
3300
|
+
ev.setUint32(0, 101010256, true);
|
|
3301
|
+
ev.setUint16(8, entries.length, true);
|
|
3302
|
+
ev.setUint16(10, entries.length, true);
|
|
3303
|
+
ev.setUint32(12, length - localsLength, true);
|
|
3304
|
+
ev.setUint32(16, localsLength, true);
|
|
3305
|
+
ev.setUint16(20, comment.length, true);
|
|
3306
|
+
end.set(comment, 22);
|
|
3307
|
+
push(end);
|
|
3308
|
+
const out = new Uint8Array(length);
|
|
3309
|
+
let pos = 0;
|
|
3310
|
+
for (const chunk of chunks) {
|
|
3311
|
+
out.set(chunk, pos);
|
|
3312
|
+
pos += chunk.length;
|
|
3313
|
+
}
|
|
3314
|
+
return out;
|
|
3315
|
+
}
|
|
3316
|
+
function dom(xml) {
|
|
3317
|
+
if (xml === null) return null;
|
|
3318
|
+
const text2 = typeof xml === "string" ? UTF8.decode(fromLatin1(xml)) : UTF8.decode(xml);
|
|
3319
|
+
if (text2 === "" || /<!DOCTYPE/i.test(text2)) return null;
|
|
3320
|
+
return parseXml(text2);
|
|
3321
|
+
}
|
|
3322
|
+
function* elements(root) {
|
|
3323
|
+
const stack = [root];
|
|
3324
|
+
while (stack.length > 0) {
|
|
3325
|
+
const el2 = stack.pop();
|
|
3326
|
+
yield el2;
|
|
3327
|
+
for (let i = el2.children.length - 1; i >= 0; i--) stack.push(el2.children[i]);
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
function firstByLocalName(root, local) {
|
|
3331
|
+
for (const el2 of elements(root)) if (el2.name === local) return el2;
|
|
3332
|
+
return null;
|
|
3333
|
+
}
|
|
3334
|
+
function asciiLower2(s) {
|
|
3335
|
+
return s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));
|
|
3336
|
+
}
|
|
3337
|
+
function latin1(bytes) {
|
|
3338
|
+
let out = "";
|
|
3339
|
+
for (let i = 0; i < bytes.length; i += 32768) out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
3340
|
+
return out;
|
|
3341
|
+
}
|
|
3342
|
+
function fromLatin1(s) {
|
|
3343
|
+
const out = new Uint8Array(s.length);
|
|
3344
|
+
for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
|
|
3345
|
+
return out;
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// src/reader/file-input.ts
|
|
3349
|
+
async function readFileBounded(path) {
|
|
3350
|
+
const fs = await import('fs');
|
|
3351
|
+
if (!fs.statSync(path).isFile()) throw new ArchiveRefused("archive_path");
|
|
3352
|
+
const fd = fs.openSync(path, "r");
|
|
3353
|
+
const limit = DEFAULT_ARCHIVE_LIMITS.maxArchiveBytes;
|
|
3354
|
+
try {
|
|
3355
|
+
const stat = fs.fstatSync(fd);
|
|
3356
|
+
if (!stat.isFile()) throw new ArchiveRefused("archive_path");
|
|
3357
|
+
if (stat.size > limit) throw new ArchiveRefused("archive_limit");
|
|
3358
|
+
const chunks = [];
|
|
3359
|
+
let total = 0;
|
|
3360
|
+
for (; ; ) {
|
|
3361
|
+
const chunk = new Uint8Array(Math.min(65536, limit + 1 - total));
|
|
3362
|
+
const count = fs.readSync(fd, chunk, 0, chunk.length, null);
|
|
3363
|
+
if (count === 0) break;
|
|
3364
|
+
if (total + count > limit) throw new ArchiveRefused("archive_limit");
|
|
3365
|
+
chunks.push(chunk.subarray(0, count));
|
|
3366
|
+
total += count;
|
|
3367
|
+
}
|
|
3368
|
+
const bytes = new Uint8Array(total);
|
|
3369
|
+
let at2 = 0;
|
|
3370
|
+
for (const chunk of chunks) {
|
|
3371
|
+
bytes.set(chunk, at2);
|
|
3372
|
+
at2 += chunk.length;
|
|
3373
|
+
}
|
|
3374
|
+
return bytes;
|
|
3375
|
+
} finally {
|
|
3376
|
+
fs.closeSync(fd);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
|
|
2672
3380
|
// src/reader/ods/ods-formula.ts
|
|
2673
3381
|
function odsFormulaToA1(formula2) {
|
|
2674
3382
|
let grammar = "of";
|
|
@@ -3177,6 +3885,7 @@ var WorkbookSchema = {
|
|
|
3177
3885
|
fromWorkbook(workbook) {
|
|
3178
3886
|
const schema = { sheets: workbook.sheets.map(sheetToSchema) };
|
|
3179
3887
|
if (Object.keys(workbook.meta).length > 0) schema["meta"] = workbook.meta;
|
|
3888
|
+
if (workbook.sourceKind === "template") schema["sourceKind"] = "template";
|
|
3180
3889
|
return schema;
|
|
3181
3890
|
}
|
|
3182
3891
|
};
|
|
@@ -3279,176 +3988,11 @@ function commentToObject(c) {
|
|
|
3279
3988
|
return out;
|
|
3280
3989
|
}
|
|
3281
3990
|
|
|
3282
|
-
// src/reader/xml.ts
|
|
3283
|
-
var XML_NS = "http://www.w3.org/XML/1998/namespace";
|
|
3284
|
-
function localName(name) {
|
|
3285
|
-
const idx = name.indexOf(":");
|
|
3286
|
-
return idx >= 0 ? name.slice(idx + 1) : name;
|
|
3287
|
-
}
|
|
3288
|
-
function unescapeXml(s) {
|
|
3289
|
-
if (s.indexOf("&") < 0) return s;
|
|
3290
|
-
return s.replace(/&(#x?[0-9a-fA-F]+|\w+);/g, (m, ent) => {
|
|
3291
|
-
switch (ent) {
|
|
3292
|
-
case "amp":
|
|
3293
|
-
return "&";
|
|
3294
|
-
case "lt":
|
|
3295
|
-
return "<";
|
|
3296
|
-
case "gt":
|
|
3297
|
-
return ">";
|
|
3298
|
-
case "quot":
|
|
3299
|
-
return '"';
|
|
3300
|
-
case "apos":
|
|
3301
|
-
return "'";
|
|
3302
|
-
default:
|
|
3303
|
-
if (ent[0] === "#") {
|
|
3304
|
-
const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
|
|
3305
|
-
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
3306
|
-
}
|
|
3307
|
-
return m;
|
|
3308
|
-
}
|
|
3309
|
-
});
|
|
3310
|
-
}
|
|
3311
|
-
function findTagEnd(src, from) {
|
|
3312
|
-
let quote2 = "";
|
|
3313
|
-
for (let i = from; i < src.length; i++) {
|
|
3314
|
-
const ch = src[i];
|
|
3315
|
-
if (quote2) {
|
|
3316
|
-
if (ch === quote2) quote2 = "";
|
|
3317
|
-
} else if (ch === '"' || ch === "'") {
|
|
3318
|
-
quote2 = ch;
|
|
3319
|
-
} else if (ch === ">") {
|
|
3320
|
-
return i;
|
|
3321
|
-
}
|
|
3322
|
-
}
|
|
3323
|
-
return src.length;
|
|
3324
|
-
}
|
|
3325
|
-
var ATTR_RE = /([^\s=/]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
3326
|
-
function parseTag(inner) {
|
|
3327
|
-
const trimmed = inner.trim();
|
|
3328
|
-
let i = 0;
|
|
3329
|
-
while (i < trimmed.length && !/\s/.test(trimmed[i])) i++;
|
|
3330
|
-
const name = trimmed.slice(0, i);
|
|
3331
|
-
const attrs = {};
|
|
3332
|
-
const raw = [];
|
|
3333
|
-
const rest = trimmed.slice(i);
|
|
3334
|
-
for (const m of rest.matchAll(ATTR_RE)) {
|
|
3335
|
-
const rawValue = m[3] ?? m[4] ?? "";
|
|
3336
|
-
attrs[localName(m[1])] = unescapeXml(rawValue);
|
|
3337
|
-
raw.push([m[1], rawValue]);
|
|
3338
|
-
}
|
|
3339
|
-
return { name, attrs, raw };
|
|
3340
|
-
}
|
|
3341
|
-
function parseXml(src, options = {}) {
|
|
3342
|
-
const namespaces = options.namespaces === true;
|
|
3343
|
-
let i = 0;
|
|
3344
|
-
const n = src.length;
|
|
3345
|
-
const root = { name: "#root", attrs: {}, children: [], text: "" };
|
|
3346
|
-
if (namespaces) root.content = [];
|
|
3347
|
-
const stack = [root];
|
|
3348
|
-
const scopes = [{ xml: XML_NS }];
|
|
3349
|
-
const appendText = (text2) => {
|
|
3350
|
-
const top = stack[stack.length - 1];
|
|
3351
|
-
top.text += text2;
|
|
3352
|
-
if (namespaces && text2 !== "") top.content.push(text2);
|
|
3353
|
-
};
|
|
3354
|
-
while (i < n) {
|
|
3355
|
-
if (src[i] === "<") {
|
|
3356
|
-
if (src.startsWith("<!--", i)) {
|
|
3357
|
-
const end2 = src.indexOf("-->", i);
|
|
3358
|
-
i = end2 < 0 ? n : end2 + 3;
|
|
3359
|
-
continue;
|
|
3360
|
-
}
|
|
3361
|
-
if (src.startsWith("<![CDATA[", i)) {
|
|
3362
|
-
const end2 = src.indexOf("]]>", i);
|
|
3363
|
-
appendText(src.slice(i + 9, end2 < 0 ? n : end2));
|
|
3364
|
-
i = end2 < 0 ? n : end2 + 3;
|
|
3365
|
-
continue;
|
|
3366
|
-
}
|
|
3367
|
-
if (src.startsWith("<?", i)) {
|
|
3368
|
-
const end2 = src.indexOf("?>", i);
|
|
3369
|
-
i = end2 < 0 ? n : end2 + 2;
|
|
3370
|
-
continue;
|
|
3371
|
-
}
|
|
3372
|
-
if (src.startsWith("<!", i)) {
|
|
3373
|
-
const end2 = src.indexOf(">", i);
|
|
3374
|
-
i = end2 < 0 ? n : end2 + 1;
|
|
3375
|
-
continue;
|
|
3376
|
-
}
|
|
3377
|
-
if (src[i + 1] === "/") {
|
|
3378
|
-
const end2 = src.indexOf(">", i);
|
|
3379
|
-
if (stack.length > 1) {
|
|
3380
|
-
stack.pop();
|
|
3381
|
-
if (namespaces) scopes.pop();
|
|
3382
|
-
}
|
|
3383
|
-
i = end2 < 0 ? n : end2 + 1;
|
|
3384
|
-
continue;
|
|
3385
|
-
}
|
|
3386
|
-
const end = findTagEnd(src, i + 1);
|
|
3387
|
-
const tagContent = src.slice(i + 1, end);
|
|
3388
|
-
const selfClosing = tagContent.endsWith("/");
|
|
3389
|
-
const { name, attrs, raw } = parseTag(selfClosing ? tagContent.slice(0, -1) : tagContent);
|
|
3390
|
-
const node = { name: localName(name), attrs, children: [], text: "" };
|
|
3391
|
-
const parent = stack[stack.length - 1];
|
|
3392
|
-
parent.children.push(node);
|
|
3393
|
-
let scope;
|
|
3394
|
-
if (namespaces) {
|
|
3395
|
-
scope = resolveNamespaces(node, name, raw, scopes[scopes.length - 1]);
|
|
3396
|
-
parent.content.push(node);
|
|
3397
|
-
}
|
|
3398
|
-
if (!selfClosing) {
|
|
3399
|
-
stack.push(node);
|
|
3400
|
-
if (scope) scopes.push(scope);
|
|
3401
|
-
}
|
|
3402
|
-
i = end + 1;
|
|
3403
|
-
} else {
|
|
3404
|
-
const next = src.indexOf("<", i);
|
|
3405
|
-
const stop = next < 0 ? n : next;
|
|
3406
|
-
appendText(unescapeXml(src.slice(i, stop)));
|
|
3407
|
-
i = stop;
|
|
3408
|
-
}
|
|
3409
|
-
}
|
|
3410
|
-
return root.children[0] ?? null;
|
|
3411
|
-
}
|
|
3412
|
-
function resolveNamespaces(node, qname, raw, parentScope) {
|
|
3413
|
-
let scope = parentScope;
|
|
3414
|
-
for (const [key, value] of raw) {
|
|
3415
|
-
if (key === "xmlns" || key.startsWith("xmlns:")) {
|
|
3416
|
-
if (scope === parentScope) scope = { ...parentScope };
|
|
3417
|
-
scope[key === "xmlns" ? "" : key.slice(6)] = unescapeXml(value);
|
|
3418
|
-
}
|
|
3419
|
-
}
|
|
3420
|
-
const colon = qname.indexOf(":");
|
|
3421
|
-
node.ns = scope[colon >= 0 ? qname.slice(0, colon) : ""] ?? "";
|
|
3422
|
-
node.content = [];
|
|
3423
|
-
const nsAttrs = {};
|
|
3424
|
-
for (const [key, value] of raw) {
|
|
3425
|
-
if (key === "xmlns" || key.startsWith("xmlns:")) continue;
|
|
3426
|
-
const idx = key.indexOf(":");
|
|
3427
|
-
const normalised = unescapeXml(value.replace(/[\t\n\r]/g, " "));
|
|
3428
|
-
if (idx < 0) {
|
|
3429
|
-
nsAttrs[key] = normalised;
|
|
3430
|
-
} else {
|
|
3431
|
-
nsAttrs[`{${scope[key.slice(0, idx)] ?? ""}}${key.slice(idx + 1)}`] = normalised;
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
node.nsAttrs = nsAttrs;
|
|
3435
|
-
return scope;
|
|
3436
|
-
}
|
|
3437
|
-
function el(node, name) {
|
|
3438
|
-
return node?.children.find((c) => c.name === name);
|
|
3439
|
-
}
|
|
3440
|
-
function els(node, name) {
|
|
3441
|
-
return node ? node.children.filter((c) => c.name === name) : [];
|
|
3442
|
-
}
|
|
3443
|
-
function at(node, name) {
|
|
3444
|
-
return node?.attrs[name];
|
|
3445
|
-
}
|
|
3446
|
-
|
|
3447
3991
|
// src/reader/ods-reader.ts
|
|
3448
3992
|
var MAX_ROWS = 1048576;
|
|
3449
3993
|
var MAX_COLUMNS = 16384;
|
|
3450
3994
|
var UNIX_EPOCH_SERIAL = 25569;
|
|
3451
|
-
var
|
|
3995
|
+
var decoder2 = new TextDecoder();
|
|
3452
3996
|
var OdsReader = class {
|
|
3453
3997
|
describe(bytes) {
|
|
3454
3998
|
return WorkbookSchema.fromWorkbook(this.readWorkbook(bytes));
|
|
@@ -3458,7 +4002,8 @@ var OdsReader = class {
|
|
|
3458
4002
|
}
|
|
3459
4003
|
/** Read an already-unzipped package (name -> bytes). */
|
|
3460
4004
|
readFiles(files) {
|
|
3461
|
-
const
|
|
4005
|
+
const kind = sourceKind(filesView(files));
|
|
4006
|
+
const getText = (name) => files[name] ? decoder2.decode(files[name]) : null;
|
|
3462
4007
|
const content = getText("content.xml");
|
|
3463
4008
|
if (content === null) throw new Error("[holy-sheet] missing content.xml");
|
|
3464
4009
|
const contentDoc = parseXml(content, { namespaces: true });
|
|
@@ -3472,7 +4017,7 @@ var OdsReader = class {
|
|
|
3472
4017
|
if (table.ns === NS.TABLE && table.name === "table") sheets.push(this.readSheet(table, styles));
|
|
3473
4018
|
}
|
|
3474
4019
|
const meta = getText("meta.xml");
|
|
3475
|
-
return new Workbook(sheets, meta === null ? {} : readMeta(meta));
|
|
4020
|
+
return new Workbook(sheets, meta === null ? {} : readMeta(meta), kind);
|
|
3476
4021
|
}
|
|
3477
4022
|
readSheet(table, styles) {
|
|
3478
4023
|
const columns = [];
|
|
@@ -3820,7 +4365,7 @@ function points(ser) {
|
|
|
3820
4365
|
byIndex.set(parseInt(idx, 10), color2);
|
|
3821
4366
|
}
|
|
3822
4367
|
}
|
|
3823
|
-
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([
|
|
4368
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index2, color2]) => ({ index: index2, color: color2 }));
|
|
3824
4369
|
}
|
|
3825
4370
|
function seriesColor(ser) {
|
|
3826
4371
|
const spPr = el(ser, "spPr");
|
|
@@ -4128,7 +4673,7 @@ function parseFrozen(xml) {
|
|
|
4128
4673
|
}
|
|
4129
4674
|
|
|
4130
4675
|
// src/reader/xlsx-reader.ts
|
|
4131
|
-
var
|
|
4676
|
+
var decoder3 = new TextDecoder();
|
|
4132
4677
|
var XlsxReader = class {
|
|
4133
4678
|
describe(bytes) {
|
|
4134
4679
|
return WorkbookSchema.fromWorkbook(this.readWorkbook(bytes));
|
|
@@ -4138,7 +4683,8 @@ var XlsxReader = class {
|
|
|
4138
4683
|
}
|
|
4139
4684
|
/** Read an already-unzipped package (name -> bytes), so a caller that sniffed it does not unzip twice. */
|
|
4140
4685
|
readFiles(files) {
|
|
4141
|
-
const
|
|
4686
|
+
const kind = sourceKind(filesView(files));
|
|
4687
|
+
const getText = (name) => files[name] ? decoder3.decode(files[name]) : null;
|
|
4142
4688
|
const stylesXml = getText("xl/styles.xml");
|
|
4143
4689
|
const stylesIndex = stylesXml ? StylesParser.parse(stylesXml) : [];
|
|
4144
4690
|
const sharedStringsXml = getText("xl/sharedStrings.xml");
|
|
@@ -4193,7 +4739,7 @@ var XlsxReader = class {
|
|
|
4193
4739
|
i++;
|
|
4194
4740
|
}
|
|
4195
4741
|
const meta = this.parseDocProps(getText("docProps/core.xml"));
|
|
4196
|
-
return new Workbook(sheets, meta);
|
|
4742
|
+
return new Workbook(sheets, meta, kind);
|
|
4197
4743
|
}
|
|
4198
4744
|
parseDocProps(coreXml) {
|
|
4199
4745
|
if (coreXml === null) return {};
|
|
@@ -5008,11 +5554,11 @@ var SheetReducer = {
|
|
|
5008
5554
|
if (!isArr(get(op, "sheet"))) {
|
|
5009
5555
|
return schema;
|
|
5010
5556
|
}
|
|
5011
|
-
const
|
|
5012
|
-
if (
|
|
5557
|
+
const index2 = has(op, "index") ? phpInteger(op.index) : sheets.length;
|
|
5558
|
+
if (index2 === null) {
|
|
5013
5559
|
return schema;
|
|
5014
5560
|
}
|
|
5015
|
-
sheets.splice(Math.max(0, Math.min(sheets.length,
|
|
5561
|
+
sheets.splice(Math.max(0, Math.min(sheets.length, index2)), 0, op.sheet);
|
|
5016
5562
|
return { ...schema, sheets };
|
|
5017
5563
|
}
|
|
5018
5564
|
const at2 = find(sheets, phpString(get(op, "sheet") ?? ""));
|
|
@@ -5188,11 +5734,11 @@ function shiftColumns(sheet, at2, delta) {
|
|
|
5188
5734
|
});
|
|
5189
5735
|
if (isArr(get(sheet, "columnWidths"))) {
|
|
5190
5736
|
const widths = /* @__PURE__ */ new Map();
|
|
5191
|
-
for (const [
|
|
5192
|
-
if (!isIndexKey(
|
|
5737
|
+
for (const [index2, width] of entriesOf(sheet.columnWidths)) {
|
|
5738
|
+
if (!isIndexKey(index2)) {
|
|
5193
5739
|
continue;
|
|
5194
5740
|
}
|
|
5195
|
-
const i = phpInt2(
|
|
5741
|
+
const i = phpInt2(index2);
|
|
5196
5742
|
const number = i + 1;
|
|
5197
5743
|
if (number < at2) {
|
|
5198
5744
|
widths.set(i, width);
|
|
@@ -5201,8 +5747,8 @@ function shiftColumns(sheet, at2, delta) {
|
|
|
5201
5747
|
}
|
|
5202
5748
|
}
|
|
5203
5749
|
const sorted = {};
|
|
5204
|
-
for (const [
|
|
5205
|
-
sorted[
|
|
5750
|
+
for (const [index2, width] of [...widths.entries()].sort((x, y) => x[0] - y[0])) {
|
|
5751
|
+
sorted[index2] = width;
|
|
5206
5752
|
}
|
|
5207
5753
|
sheet = setOrUnset(sheet, "columnWidths", sorted, EMPTY);
|
|
5208
5754
|
}
|
|
@@ -5465,22 +6011,22 @@ function granular(a, b) {
|
|
|
5465
6011
|
ops.push({ type: "rename_sheet", sheet: name, name: renames.get(name) });
|
|
5466
6012
|
}
|
|
5467
6013
|
}
|
|
5468
|
-
namesB.forEach((name,
|
|
6014
|
+
namesB.forEach((name, index2) => {
|
|
5469
6015
|
if (unmatchedAdded.includes(name)) {
|
|
5470
|
-
ops.push({ type: "add_sheet", index, sheet: sheetsB[
|
|
6016
|
+
ops.push({ type: "add_sheet", index: index2, sheet: sheetsB[index2] });
|
|
5471
6017
|
}
|
|
5472
6018
|
});
|
|
5473
6019
|
let state = SheetReducer.applyAll(a, ops);
|
|
5474
|
-
namesB.forEach((name,
|
|
6020
|
+
namesB.forEach((name, index2) => {
|
|
5475
6021
|
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:
|
|
6022
|
+
if ((current.get(String(index2)) ?? null) !== name) {
|
|
6023
|
+
const op = { type: "move_sheet", sheet: name, toIndex: index2 };
|
|
5478
6024
|
ops.push(op);
|
|
5479
6025
|
state = SheetReducer.apply(state, op);
|
|
5480
6026
|
}
|
|
5481
6027
|
});
|
|
5482
|
-
sheetsB.forEach((target,
|
|
5483
|
-
const current = state.sheets[
|
|
6028
|
+
sheetsB.forEach((target, index2) => {
|
|
6029
|
+
const current = state.sheets[index2];
|
|
5484
6030
|
if (SheetDiff.same(current, target)) {
|
|
5485
6031
|
return;
|
|
5486
6032
|
}
|
|
@@ -6131,7 +6677,7 @@ var holy_sheet_schema_default = {
|
|
|
6131
6677
|
};
|
|
6132
6678
|
|
|
6133
6679
|
// src/agent.ts
|
|
6134
|
-
var VERSION = "2.
|
|
6680
|
+
var VERSION = "2.11.0";
|
|
6135
6681
|
function toU8(input) {
|
|
6136
6682
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
6137
6683
|
}
|
|
@@ -6172,12 +6718,38 @@ var Agent = {
|
|
|
6172
6718
|
read(input) {
|
|
6173
6719
|
return readSpreadsheet(toU8(input), null);
|
|
6174
6720
|
},
|
|
6721
|
+
/**
|
|
6722
|
+
* Turn an Excel `.xltx` / LibreOffice `.ots` template into its `.xlsx` /
|
|
6723
|
+
* `.ods` twin, or back. `to` is `"document"`, `"template"`, or null to flip.
|
|
6724
|
+
* Universal and synchronous: bytes in, bytes out. Mirrors PHP
|
|
6725
|
+
* `Agent::convertTemplate()`.
|
|
6726
|
+
*
|
|
6727
|
+
* Only the declaration moves: the workbook part's content type in
|
|
6728
|
+
* `[Content_Types].xml`, or the ODF `mimetype` entry together with the
|
|
6729
|
+
* manifest's root media-type (ODF 1.3 requires them to agree). Every other
|
|
6730
|
+
* member is copied as its original zip record, byte for byte.
|
|
6731
|
+
*
|
|
6732
|
+
* Throws `OfficeTemplateRefused` (macro-enabled, mismatched, already that
|
|
6733
|
+
* kind), `ArchiveRefused` (archive guard), or `UnsupportedFormatException`
|
|
6734
|
+
* for a package that is not a spreadsheet.
|
|
6735
|
+
*/
|
|
6736
|
+
convertTemplate(input, to = null) {
|
|
6737
|
+
const bytes = toU8(input);
|
|
6738
|
+
const zip = SourceArchive.open(bytes);
|
|
6739
|
+
const raw = zip.read("mimetype");
|
|
6740
|
+
const mimetype = raw === null ? null : new TextDecoder().decode(raw).trim();
|
|
6741
|
+
const odf = mimetype !== null && mimetype.startsWith("application/vnd.oasis.opendocument.spreadsheet");
|
|
6742
|
+
if (!odf && (mimetype !== null || zip.read("xl/workbook.xml") === null)) {
|
|
6743
|
+
throw UnsupportedFormatException.unknownPackage(null, mimetype);
|
|
6744
|
+
}
|
|
6745
|
+
return convert(bytes, to);
|
|
6746
|
+
},
|
|
6175
6747
|
/** Round-trip an xlsx or ods file on disk back to a schema (Node only). */
|
|
6176
6748
|
async describe(path) {
|
|
6177
6749
|
const fs = await import('fs');
|
|
6178
6750
|
if (!fs.existsSync(path)) return { error: "not_found", path };
|
|
6179
|
-
const bytes =
|
|
6180
|
-
return readSpreadsheet(
|
|
6751
|
+
const bytes = await readFileBounded(path);
|
|
6752
|
+
return readSpreadsheet(bytes, path);
|
|
6181
6753
|
},
|
|
6182
6754
|
/** Validate + conservative repairs in one call. */
|
|
6183
6755
|
validateAndRepair(schema) {
|
|
@@ -6273,6 +6845,10 @@ var HolySheet = class {
|
|
|
6273
6845
|
describe(path) {
|
|
6274
6846
|
return Agent.describe(path);
|
|
6275
6847
|
}
|
|
6848
|
+
/** {@link Agent.convertTemplate}: .xltx/.ots to .xlsx/.ods and back. */
|
|
6849
|
+
convertTemplate(input, to = null) {
|
|
6850
|
+
return Agent.convertTemplate(input, to);
|
|
6851
|
+
}
|
|
6276
6852
|
validateAndRepair(schema) {
|
|
6277
6853
|
return Agent.validateAndRepair(schema);
|
|
6278
6854
|
}
|
|
@@ -6295,6 +6871,7 @@ var HolySheet = class {
|
|
|
6295
6871
|
HolySheet.VERSION = VERSION;
|
|
6296
6872
|
|
|
6297
6873
|
exports.Agent = Agent;
|
|
6874
|
+
exports.ArchiveRefused = ArchiveRefused;
|
|
6298
6875
|
exports.ArrayBuilder = ArrayBuilder;
|
|
6299
6876
|
exports.CellAddress = CellAddress;
|
|
6300
6877
|
exports.CsvBuilder = CsvBuilder;
|
|
@@ -6304,6 +6881,7 @@ exports.HolySheet = HolySheet;
|
|
|
6304
6881
|
exports.Inference = Inference;
|
|
6305
6882
|
exports.Normalizer = Normalizer;
|
|
6306
6883
|
exports.OdsReader = OdsReader;
|
|
6884
|
+
exports.OfficeTemplateRefused = OfficeTemplateRefused;
|
|
6307
6885
|
exports.Repairer = Repairer;
|
|
6308
6886
|
exports.SchemaException = SchemaException;
|
|
6309
6887
|
exports.SheetDiff = SheetDiff;
|