@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/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(index) {
246
- if (index < 0) {
247
- throw new Error(`[holy-sheet] column index must be \u2265 0, got ${index}`);
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 = index;
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 index = parseInt(key, 10);
304
- return index <= ColumnWidths.MAX_INDEX ? index : null;
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 index = ColumnWidths.index(colIdx);
829
+ const index2 = ColumnWidths.index(colIdx);
804
830
  const width = ColumnWidths.width(columnDef.width);
805
- if (index === null || width === null) continue;
806
- out.set(index, width);
831
+ if (index2 === null || width === null) continue;
832
+ out.set(index2, width);
807
833
  }
808
- for (const [index, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
809
- out.set(index, width);
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 index = ColumnWidths.index(key);
842
+ const index2 = ColumnWidths.index(key);
817
843
  const width = ColumnWidths.width(px);
818
- if (index === null || width === null) continue;
819
- out.set(index, width);
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 index = rec ? rec.index : void 0;
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 index === "number" && Number.isInteger(index) && index >= 0 && index < count && m) {
935
- byIndex.set(index, m[1].toUpperCase());
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(([index, color2]) => ({ index, color: color2 }));
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 index = /* @__PURE__ */ new Map();
1098
+ const index2 = /* @__PURE__ */ new Map();
1073
1099
  for (const sheet of wb.sheets) {
1074
1100
  for (const [address, cell] of sheet.cells) {
1075
- index.set(sheet.name + "!" + address, cell);
1101
+ index2.set(sheet.name + "!" + address, cell);
1076
1102
  }
1077
1103
  }
1078
- return index;
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 index = ColumnWidths.index(key);
1593
- if (index === null) {
1618
+ let index2 = ColumnWidths.index(key);
1619
+ if (index2 === null) {
1594
1620
  const letters = ColumnWidths.fromLetters(key);
1595
1621
  if (letters !== null) {
1596
- index = letters;
1597
- this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${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 (index === null || ColumnWidths.width(px) === null) {
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(index)] = px;
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 index = rec ? rec.index ?? null : null;
2056
+ const index2 = rec ? rec.index ?? null : null;
2031
2057
  const color2 = rec ? rec.color ?? null : null;
2032
- if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= count) {
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(index),
2037
- index,
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++) t.table[lengths[off + 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
- for (let length = readBits(d, 2, 3); length; length--) lengths[num++] = prev;
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
- for (let length = readBits(d, 3, 3); length; length--) lengths[num++] = 0;
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
- case 18:
2503
- for (let length = readBits(d, 7, 11); length; length--) lengths[num++] = 0;
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.buf = new Uint8Array(1024);
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/zip-reader.ts
2574
- var decoder = new TextDecoder();
2575
- function unzipSync(data) {
2576
- const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
2577
- let eocd = -1;
2578
- for (let i = data.length - 22; i >= 0; i--) {
2579
- if (dv.getUint32(i, true) === 101010256) {
2580
- eocd = i;
2581
- break;
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
- if (eocd < 0) throw new Error("holy-sheet: not a zip archive (no EOCD record)");
2585
- const count = dv.getUint16(eocd + 10, true);
2586
- let cd = dv.getUint32(eocd + 16, true);
2587
- const result = {};
2588
- for (let n = 0; n < count; n++) {
2589
- if (dv.getUint32(cd, true) !== 33639248) break;
2590
- const method = dv.getUint16(cd + 10, true);
2591
- const compSize = dv.getUint32(cd + 20, true);
2592
- const nameLen = dv.getUint16(cd + 28, true);
2593
- const extraLen = dv.getUint16(cd + 30, true);
2594
- const commentLen = dv.getUint16(cd + 32, true);
2595
- const localOffset = dv.getUint32(cd + 42, true);
2596
- const name = decoder.decode(data.subarray(cd + 46, cd + 46 + nameLen));
2597
- const lhNameLen = dv.getUint16(localOffset + 26, true);
2598
- const lhExtraLen = dv.getUint16(localOffset + 28, true);
2599
- const dataStart = localOffset + 30 + lhNameLen + lhExtraLen;
2600
- const raw = data.subarray(dataStart, dataStart + compSize);
2601
- let content;
2602
- if (method === 0) content = raw.slice();
2603
- else if (method === 8) content = inflateRaw(raw);
2604
- else throw new Error(`holy-sheet: unsupported zip method ${method} for "${name}"`);
2605
- result[name] = content;
2606
- cd += 46 + nameLen + extraLen + commentLen;
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 decoder2 = new TextDecoder();
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(decoder2.decode(files["mimetype"])) : null;
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 decoder3 = new TextDecoder();
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 getText = (name) => files[name] ? decoder3.decode(files[name]) : null;
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(([index, color2]) => ({ index, color: color2 }));
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 decoder4 = new TextDecoder();
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 getText = (name) => files[name] ? decoder4.decode(files[name]) : null;
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 index = has(op, "index") ? phpInteger(op.index) : sheets.length;
5012
- if (index === null) {
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, index)), 0, op.sheet);
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 [index, width] of entriesOf(sheet.columnWidths)) {
5192
- if (!isIndexKey(index)) {
5737
+ for (const [index2, width] of entriesOf(sheet.columnWidths)) {
5738
+ if (!isIndexKey(index2)) {
5193
5739
  continue;
5194
5740
  }
5195
- const i = phpInt2(index);
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 [index, width] of [...widths.entries()].sort((x, y) => x[0] - y[0])) {
5205
- sorted[index] = width;
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, index) => {
6014
+ namesB.forEach((name, index2) => {
5469
6015
  if (unmatchedAdded.includes(name)) {
5470
- ops.push({ type: "add_sheet", index, sheet: sheetsB[index] });
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, index) => {
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(index)) ?? null) !== name) {
5477
- const op = { type: "move_sheet", sheet: name, toIndex: index };
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, index) => {
5483
- const current = state.sheets[index];
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.10.1";
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 = fs.readFileSync(path);
6180
- return readSpreadsheet(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength), path);
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;