@devmm/puredocs-excel 1.0.4 → 1.0.5

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
@@ -310,19 +310,26 @@ var CONTENT_TYPES = {
310
310
  };
311
311
 
312
312
  // src/io/ooxml-templates.ts
313
- function buildContentTypes(sheetCount, extraOverrides = []) {
313
+ function buildContentTypes(sheetCount, extraOverrides = [], extraDefaults = []) {
314
314
  const overrides = Array.from(
315
315
  { length: sheetCount },
316
316
  (_, i) => ` <Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="${CONTENT_TYPES.worksheet}"/>`
317
317
  );
318
- for (const { partName, contentType } of extraOverrides) {
318
+ const seen = /* @__PURE__ */ new Set();
319
+ for (const { partName, contentType } of [...extraOverrides].reverse()) {
320
+ if (seen.has(partName)) continue;
321
+ seen.add(partName);
319
322
  overrides.push(` <Override PartName="${partName}" ContentType="${contentType}"/>`);
320
323
  }
324
+ const defaults = extraDefaults.map(
325
+ ({ extension, contentType }) => ` <Default Extension="${extension}" ContentType="${contentType}"/>`
326
+ );
321
327
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
322
328
  <Types xmlns="${"http://schemas.openxmlformats.org/package/2006/content-types"}">
323
329
  <Default Extension="rels" ContentType="${CONTENT_TYPES.relationships}"/>
324
330
  <Default Extension="xml" ContentType="application/xml"/>
325
- <Override PartName="/xl/workbook.xml" ContentType="${CONTENT_TYPES.workbook}"/>
331
+ ${defaults.length > 0 ? `${defaults.join("\n")}
332
+ ` : ""} <Override PartName="/xl/workbook.xml" ContentType="${CONTENT_TYPES.workbook}"/>
326
333
  <Override PartName="/xl/styles.xml" ContentType="${CONTENT_TYPES.styles}"/>
327
334
  <Override PartName="/xl/sharedStrings.xml" ContentType="${CONTENT_TYPES.sharedStrings}"/>
328
335
  ${overrides.join("\n")}
@@ -401,6 +408,94 @@ ${relationships.join("\n")}
401
408
  </Relationships>`;
402
409
  }
403
410
 
411
+ // src/io/preserved-parts.ts
412
+ function isCoreOwned(path) {
413
+ return path === "[Content_Types].xml" || path === "_rels/.rels" || path === "xl/workbook.xml" || path === "xl/_rels/workbook.xml.rels" || path === "xl/styles.xml" || path === "xl/sharedStrings.xml" || path.startsWith("xl/worksheets/");
414
+ }
415
+ function collectPreservedParts(entries) {
416
+ const preserved = /* @__PURE__ */ new Map();
417
+ for (const [path, bytes] of entries) {
418
+ if (!isCoreOwned(path)) preserved.set(path, bytes);
419
+ }
420
+ return preserved;
421
+ }
422
+ function parseContentTypes(xml2) {
423
+ const defaults = /* @__PURE__ */ new Map();
424
+ const overrides = /* @__PURE__ */ new Map();
425
+ for (const m of xml2.matchAll(/<Default\s+[^>]*\/?>/g)) {
426
+ const ext = /Extension="([^"]*)"/.exec(m[0])?.[1];
427
+ const ct = /ContentType="([^"]*)"/.exec(m[0])?.[1];
428
+ if (ext && ct) defaults.set(ext.toLowerCase(), ct);
429
+ }
430
+ for (const m of xml2.matchAll(/<Override\s+[^>]*\/?>/g)) {
431
+ const part = /PartName="([^"]*)"/.exec(m[0])?.[1];
432
+ const ct = /ContentType="([^"]*)"/.exec(m[0])?.[1];
433
+ if (part && ct) overrides.set(part, ct);
434
+ }
435
+ return { defaults, overrides };
436
+ }
437
+ function resolveTarget(target, baseDir) {
438
+ if (target.startsWith("/")) return target.slice(1);
439
+ const segments = baseDir.split("/").filter(Boolean);
440
+ for (const seg of target.split("/")) {
441
+ if (seg === "." || seg === "") continue;
442
+ if (seg === "..") segments.pop();
443
+ else segments.push(seg);
444
+ }
445
+ return segments.join("/");
446
+ }
447
+ function parseRelationships(relsXml, ownerPath) {
448
+ const baseDir = ownerPath.slice(0, ownerPath.lastIndexOf("/"));
449
+ const rels = [];
450
+ for (const m of relsXml.matchAll(/<Relationship\s+[^>]*\/?>/g)) {
451
+ const tag = m[0];
452
+ const id = /\bId="([^"]*)"/.exec(tag)?.[1];
453
+ const type = /\bType="([^"]*)"/.exec(tag)?.[1];
454
+ const target = /\bTarget="([^"]*)"/.exec(tag)?.[1];
455
+ if (!id || !type || target === void 0) continue;
456
+ const external = /TargetMode="External"/.test(tag);
457
+ rels.push({
458
+ id,
459
+ type,
460
+ target,
461
+ resolved: external ? void 0 : resolveTarget(target, baseDir),
462
+ external
463
+ });
464
+ }
465
+ return rels;
466
+ }
467
+ function collectSubtree(parts, startPath) {
468
+ const seen = /* @__PURE__ */ new Set();
469
+ const queue = [startPath];
470
+ while (queue.length > 0) {
471
+ const path = queue.pop();
472
+ if (seen.has(path) || !parts.has(path)) continue;
473
+ seen.add(path);
474
+ const dir = path.slice(0, path.lastIndexOf("/"));
475
+ const file = path.slice(path.lastIndexOf("/") + 1);
476
+ const relsPath = `${dir}/_rels/${file}.rels`;
477
+ const relsBytes = parts.get(relsPath);
478
+ if (!relsBytes) continue;
479
+ seen.add(relsPath);
480
+ for (const rel of parseRelationships(decodeUtf8(relsBytes), path)) {
481
+ if (rel.resolved) queue.push(rel.resolved);
482
+ }
483
+ }
484
+ return seen;
485
+ }
486
+ var PRESERVED_TAIL_TAGS = ["drawing", "legacyDrawing", "legacyDrawingHF", "picture"];
487
+ function parseTailElements(worksheetXml) {
488
+ const found = [];
489
+ for (const tag of PRESERVED_TAIL_TAGS) {
490
+ const re = new RegExp(`<(?:\\w+:)?${tag}\\s[^>]*?/>|<(?:\\w+:)?${tag}\\s[^>]*?>\\s*</(?:\\w+:)?${tag}>`, "g");
491
+ for (const m of worksheetXml.matchAll(re)) {
492
+ const relId = /r:id="([^"]*)"/.exec(m[0])?.[1];
493
+ if (relId) found.push({ tag, relId, xml: m[0] });
494
+ }
495
+ }
496
+ return found;
497
+ }
498
+
404
499
  // src/model/shared-string-manager.ts
405
500
  var _stringToIndex, _indexToString;
406
501
  var SharedStringManager = class {
@@ -1550,6 +1645,129 @@ function escAttr(s) {
1550
1645
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1551
1646
  }
1552
1647
 
1648
+ // src/style/number-format-renderer.ts
1649
+ function splitSections(code) {
1650
+ const sections = [];
1651
+ let current = "";
1652
+ let inQuote = false;
1653
+ for (let i = 0; i < code.length; i++) {
1654
+ const ch = code[i];
1655
+ if (ch === '"') {
1656
+ inQuote = !inQuote;
1657
+ current += ch;
1658
+ continue;
1659
+ }
1660
+ if (ch === "\\" && i + 1 < code.length) {
1661
+ current += ch + code[++i];
1662
+ continue;
1663
+ }
1664
+ if (ch === ";" && !inQuote) {
1665
+ sections.push(current);
1666
+ current = "";
1667
+ continue;
1668
+ }
1669
+ current += ch;
1670
+ }
1671
+ sections.push(current);
1672
+ return sections;
1673
+ }
1674
+ function isUnsupported(section) {
1675
+ if (/\[[<>=]/.test(section)) return true;
1676
+ if (/[?]/.test(section)) return true;
1677
+ if (/[Ee][+-]/.test(section)) return true;
1678
+ return false;
1679
+ }
1680
+ function parseSection(section) {
1681
+ const stripped = section.replace(
1682
+ /\[(\$([^\]-]*)[^\]]*|[^\]]*)\]/g,
1683
+ (_m, _all, currency) => typeof currency === "string" ? currency : ""
1684
+ );
1685
+ let prefix = "";
1686
+ let suffix = "";
1687
+ let pattern = "";
1688
+ let seenPattern = false;
1689
+ let percentScale = 1;
1690
+ for (let i = 0; i < stripped.length; i++) {
1691
+ const ch = stripped[i];
1692
+ if (ch === '"') {
1693
+ const end = stripped.indexOf('"', i + 1);
1694
+ const literal = stripped.slice(i + 1, end < 0 ? void 0 : end);
1695
+ if (seenPattern) suffix += literal;
1696
+ else prefix += literal;
1697
+ i = end < 0 ? stripped.length : end;
1698
+ continue;
1699
+ }
1700
+ if (ch === "\\" && i + 1 < stripped.length) {
1701
+ const literal = stripped[++i];
1702
+ if (seenPattern) suffix += literal;
1703
+ else prefix += literal;
1704
+ continue;
1705
+ }
1706
+ if (ch === "_" || ch === "*") {
1707
+ i++;
1708
+ continue;
1709
+ }
1710
+ if (ch === "#" || ch === "0" || ch === "." || ch === ",") {
1711
+ pattern += ch;
1712
+ seenPattern = true;
1713
+ continue;
1714
+ }
1715
+ if (ch === "%") {
1716
+ percentScale *= 100;
1717
+ if (seenPattern) suffix += ch;
1718
+ else prefix += ch;
1719
+ continue;
1720
+ }
1721
+ if (seenPattern) suffix += ch;
1722
+ else prefix += ch;
1723
+ }
1724
+ const dot = pattern.indexOf(".");
1725
+ const intPart = dot < 0 ? pattern : pattern.slice(0, dot);
1726
+ const fracPart = dot < 0 ? "" : pattern.slice(dot + 1);
1727
+ return {
1728
+ literalOnly: !seenPattern,
1729
+ prefix,
1730
+ suffix,
1731
+ minIntDigits: (intPart.match(/0/g) ?? []).length,
1732
+ decimals: (fracPart.match(/[#0]/g) ?? []).length,
1733
+ useGrouping: intPart.includes(","),
1734
+ percentScale
1735
+ };
1736
+ }
1737
+ function renderNumber(value, p) {
1738
+ if (p.literalOnly) return p.prefix + p.suffix;
1739
+ const scaled = value * p.percentScale;
1740
+ const fixed = scaled.toFixed(p.decimals);
1741
+ let [intDigits = "0", fracDigits = ""] = fixed.split(".");
1742
+ if (intDigits.length < p.minIntDigits) {
1743
+ intDigits = intDigits.padStart(p.minIntDigits, "0");
1744
+ }
1745
+ if (p.useGrouping) {
1746
+ intDigits = intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1747
+ }
1748
+ return p.prefix + intDigits + (fracDigits ? `.${fracDigits}` : "") + p.suffix;
1749
+ }
1750
+ function formatNumberWithCode(value, formatCode) {
1751
+ if (!Number.isFinite(value)) return null;
1752
+ if (!formatCode || formatCode === "General") return null;
1753
+ if (formatCode === "@") return String(value);
1754
+ const sections = splitSections(formatCode);
1755
+ const negative = sections[1];
1756
+ const zero = sections[2];
1757
+ let section;
1758
+ let magnitude = value;
1759
+ if (value === 0 && zero !== void 0) {
1760
+ section = zero;
1761
+ } else if (value < 0 && negative !== void 0) {
1762
+ section = negative;
1763
+ magnitude = Math.abs(value);
1764
+ } else {
1765
+ section = sections[0];
1766
+ }
1767
+ if (section === void 0 || isUnsupported(section)) return null;
1768
+ return renderNumber(magnitude, parseSection(section));
1769
+ }
1770
+
1553
1771
  // src/model/cell-reference.ts
1554
1772
  function parseCellRef(cellReference) {
1555
1773
  if (!cellReference) {
@@ -1749,6 +1967,9 @@ var Cell = class {
1749
1967
  }
1750
1968
  /**
1751
1969
  * Returns the display text of the cell value (always a string).
1970
+ *
1971
+ * Numbers are returned unformatted; use {@link getFormattedText} to apply the
1972
+ * cell's number format.
1752
1973
  */
1753
1974
  getText() {
1754
1975
  const val = this.getValue();
@@ -1756,6 +1977,21 @@ var Cell = class {
1756
1977
  if (val instanceof Date) return val.toLocaleDateString();
1757
1978
  return String(val);
1758
1979
  }
1980
+ /**
1981
+ * Returns the display text with the cell's number format applied, e.g. a
1982
+ * value of 4.5 under `$#,##0.00` renders as `$4.50`.
1983
+ *
1984
+ * Falls back to {@link getText} for non-numeric values and for format codes
1985
+ * outside the supported subset (fractions, scientific notation, conditional
1986
+ * sections) — so the result is never a misleading rendering.
1987
+ */
1988
+ getFormattedText() {
1989
+ const val = this.getValue();
1990
+ if (typeof val !== "number") return this.getText();
1991
+ const format = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).numberFormat;
1992
+ if (!format) return this.getText();
1993
+ return formatNumberWithCode(val, format.formatCode) ?? this.getText();
1994
+ }
1759
1995
  // ── Formula API ────────────────────────────────────────────────────────────
1760
1996
  /**
1761
1997
  * Sets a formula. The leading '=' is optional.
@@ -2084,7 +2320,7 @@ forEach_fn = function(action) {
2084
2320
  };
2085
2321
 
2086
2322
  // src/model/worksheet.ts
2087
- var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _Worksheet_instances, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, getOrCreateRow_fn;
2323
+ var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _preservedTail, _preservedRels, _Worksheet_instances, activeTail_fn, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, getOrCreateRow_fn;
2088
2324
  var Worksheet = class {
2089
2325
  /** @internal */
2090
2326
  constructor(name, sharedStrings, styles) {
@@ -2100,6 +2336,12 @@ var Worksheet = class {
2100
2336
  __privateAdd(this, _dataValidations, []);
2101
2337
  __privateAdd(this, _visibility, "visible");
2102
2338
  __privateAdd(this, _drawings, []);
2339
+ // Round-trip state captured when this sheet was loaded from a file: the
2340
+ // relationship-bearing leaf elements of the worksheet part, and the sheet's
2341
+ // original relationships. Re-emitted on save so drawings, images and legacy
2342
+ // comment anchors survive open→save. See io/preserved-parts.ts.
2343
+ __privateAdd(this, _preservedTail, []);
2344
+ __privateAdd(this, _preservedRels, []);
2103
2345
  __privateSet(this, _name, name);
2104
2346
  __privateSet(this, _sharedStrings2, sharedStrings);
2105
2347
  __privateSet(this, _styles2, styles);
@@ -2179,6 +2421,16 @@ var Worksheet = class {
2179
2421
  __privateGet(this, _cols).push({ min: columnIndex, max: columnIndex, width, customWidth: true });
2180
2422
  __privateGet(this, _cols).sort((a, b) => a.min - b.min);
2181
2423
  }
2424
+ /**
2425
+ * Returns the explicit width of a column (1-based index), or `undefined` when
2426
+ * the column uses the sheet default. Counterpart to {@link setColumnWidth};
2427
+ * widths parsed from an existing file are visible here too, so column layout
2428
+ * can be round-tripped.
2429
+ */
2430
+ getColumnWidth(columnIndex) {
2431
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2432
+ return __privateGet(this, _cols).find((c) => c.min <= columnIndex && c.max >= columnIndex)?.width;
2433
+ }
2182
2434
  /** Auto-fits column width based on content (approximate). */
2183
2435
  autoFitColumn(columnIndex) {
2184
2436
  if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
@@ -2202,6 +2454,14 @@ var Worksheet = class {
2202
2454
  row.height = height;
2203
2455
  row.customHeight = true;
2204
2456
  }
2457
+ /**
2458
+ * Returns the explicit height of a row (1-based index), or `undefined` when
2459
+ * the row uses the sheet default. Counterpart to {@link setRowHeight}.
2460
+ */
2461
+ getRowHeight(rowIndex) {
2462
+ if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
2463
+ return __privateGet(this, _rows).get(rowIndex)?.height;
2464
+ }
2205
2465
  /** Shows or hides a row (1-based index). Hidden rows affect SUBTOTAL(101-111). */
2206
2466
  setRowHidden(rowIndex, hidden = true) {
2207
2467
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
@@ -2220,6 +2480,14 @@ var Worksheet = class {
2220
2480
  unmergeCells(rangeReference) {
2221
2481
  __privateSet(this, _mergeCells, __privateGet(this, _mergeCells).filter((m) => m.ref !== rangeReference));
2222
2482
  }
2483
+ /**
2484
+ * All merged ranges on this sheet, in the order they were added (or parsed).
2485
+ * Includes merges read from an existing file, so callers never need to track
2486
+ * their own copy of the list.
2487
+ */
2488
+ get merges() {
2489
+ return __privateGet(this, _mergeCells).map((m) => m.ref);
2490
+ }
2223
2491
  /**
2224
2492
  * Freezes rows and/or columns.
2225
2493
  * @param row Number of rows to freeze (0 = none)
@@ -2258,6 +2526,18 @@ var Worksheet = class {
2258
2526
  addDrawingProvider(provider) {
2259
2527
  __privateGet(this, _drawings).push(provider);
2260
2528
  }
2529
+ /**
2530
+ * Detaches every drawing from this worksheet — both providers added in memory
2531
+ * and any drawing preserved from the file the workbook was opened from.
2532
+ *
2533
+ * Call before re-adding charts when exporting the same in-memory workbook
2534
+ * repeatedly, otherwise each export appends another copy; call on its own to
2535
+ * deliberately drop a chart that came from the source file.
2536
+ */
2537
+ clearDrawings() {
2538
+ __privateSet(this, _drawings, []);
2539
+ __privateSet(this, _preservedTail, __privateGet(this, _preservedTail).filter((t) => t.tag !== "drawing"));
2540
+ }
2261
2541
  /** True when this worksheet has at least one drawing to serialise. */
2262
2542
  get hasDrawings() {
2263
2543
  return __privateGet(this, _drawings).length > 0;
@@ -2266,6 +2546,44 @@ var Worksheet = class {
2266
2546
  get drawingProviders() {
2267
2547
  return __privateGet(this, _drawings);
2268
2548
  }
2549
+ // ── Round-trip preservation (internal) ───────────────────────────────────────
2550
+ /**
2551
+ * @internal Relationships carried over from the source file, minus any whose
2552
+ * leaf element is no longer emitted (a drawing replaced by live providers, or
2553
+ * cleared outright). The save pipeline writes these into the sheet's rels.
2554
+ */
2555
+ get preservedRelationships() {
2556
+ const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
2557
+ return __privateGet(this, _preservedRels).filter((r) => liveIds.has(r.id));
2558
+ }
2559
+ /**
2560
+ * @internal Package paths of relationships that were preserved at load time
2561
+ * but are no longer referenced — a drawing replaced by live providers, or one
2562
+ * cleared outright. The save pipeline prunes these parts so the output has no
2563
+ * orphans.
2564
+ */
2565
+ get droppedPreservedTargets() {
2566
+ const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
2567
+ return __privateGet(this, _preservedRels).filter((r) => !liveIds.has(r.id) && r.resolved !== void 0).map((r) => r.resolved);
2568
+ }
2569
+ /**
2570
+ * @internal Relationship id for a drawing built from live providers. Chosen to
2571
+ * clear every preserved id so the two sets cannot collide.
2572
+ */
2573
+ get drawingRelId() {
2574
+ let max = 0;
2575
+ for (const r of __privateGet(this, _preservedRels)) {
2576
+ const n = /^rId(\d+)$/.exec(r.id);
2577
+ if (n) max = Math.max(max, parseInt(n[1], 10));
2578
+ }
2579
+ return `rId${max + 1}`;
2580
+ }
2581
+ /** @internal Captures round-trip state from the source package. */
2582
+ loadPreservedState(worksheetXml, relationships) {
2583
+ __privateSet(this, _preservedRels, [...relationships]);
2584
+ const known = new Set(relationships.map((r) => r.id));
2585
+ __privateSet(this, _preservedTail, parseTailElements(worksheetXml).filter((t) => known.has(t.relId)));
2586
+ }
2269
2587
  // ── XML serialisation ──────────────────────────────────────────────────────
2270
2588
  /** Builds the xl/worksheets/sheetN.xml string. */
2271
2589
  buildXml() {
@@ -2275,12 +2593,13 @@ var Worksheet = class {
2275
2593
  const filterXml = __privateGet(this, _autoFilter) ? `<autoFilter ref="${__privateGet(this, _autoFilter).ref}"/>` : "";
2276
2594
  const validXml = __privateMethod(this, _Worksheet_instances, buildDataValidationsXml_fn).call(this);
2277
2595
  const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2278
- const drawingXml = this.hasDrawings ? `<drawing r:id="${WORKSHEET_DRAWING_REL_ID}"/>` : "";
2596
+ const preservedTailXml = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.xml).join("");
2597
+ const drawingXml = this.hasDrawings ? `<drawing r:id="${this.drawingRelId}"/>` : "";
2279
2598
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2280
2599
  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
2281
2600
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
2282
2601
  <sheetViews>${sheetViewXml}</sheetViews>
2283
- ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${drawingXml}
2602
+ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${drawingXml}
2284
2603
  </worksheet>`;
2285
2604
  }
2286
2605
  /** Parses xl/worksheets/sheetN.xml and loads cells into memory. */
@@ -2361,7 +2680,17 @@ _autoFilter = new WeakMap();
2361
2680
  _dataValidations = new WeakMap();
2362
2681
  _visibility = new WeakMap();
2363
2682
  _drawings = new WeakMap();
2683
+ _preservedTail = new WeakMap();
2684
+ _preservedRels = new WeakMap();
2364
2685
  _Worksheet_instances = new WeakSet();
2686
+ /**
2687
+ * Preserved leaf elements that will actually be emitted. A sheet may only have
2688
+ * one `<drawing>`, so live providers suppress a preserved one — every consumer
2689
+ * (serialisation, relationships, pruning) must agree on that, hence one helper.
2690
+ */
2691
+ activeTail_fn = function() {
2692
+ return __privateGet(this, _preservedTail).filter((t) => !(t.tag === "drawing" && this.hasDrawings));
2693
+ };
2365
2694
  // ── Private XML builders ───────────────────────────────────────────────────
2366
2695
  buildColsXml_fn = function() {
2367
2696
  if (__privateGet(this, _cols).length === 0) return "";
@@ -2486,7 +2815,7 @@ indexOf_fn = function(name, scope) {
2486
2815
  };
2487
2816
 
2488
2817
  // src/model/workbook.ts
2489
- var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _Workbook_instances, assertOpen_fn, buildZipEntries_fn;
2818
+ var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
2490
2819
  var _Workbook = class _Workbook {
2491
2820
  constructor(sharedStrings, styles) {
2492
2821
  __privateAdd(this, _Workbook_instances);
@@ -2495,6 +2824,11 @@ var _Workbook = class _Workbook {
2495
2824
  __privateAdd(this, _sheets, []);
2496
2825
  __privateAdd(this, _definedNames, new DefinedNameManager());
2497
2826
  __privateAdd(this, _closed, false);
2827
+ /** Parts carried over verbatim from the file this workbook was opened from. */
2828
+ __privateAdd(this, _preservedParts, /* @__PURE__ */ new Map());
2829
+ __privateAdd(this, _preservedContentTypes);
2830
+ /** Preserved targets whose referring worksheet has been removed. */
2831
+ __privateAdd(this, _orphanedTargets, []);
2498
2832
  __privateSet(this, _sharedStrings3, sharedStrings);
2499
2833
  __privateSet(this, _styles3, styles);
2500
2834
  }
@@ -2535,8 +2869,19 @@ var _Workbook = class _Workbook {
2535
2869
  const ws = new Worksheet(name, ss, sm);
2536
2870
  if (state) ws.setVisibility(state);
2537
2871
  ws.loadFromXml(wsXml);
2872
+ if (wsPath) {
2873
+ const dir = wsPath.slice(0, wsPath.lastIndexOf("/"));
2874
+ const file = wsPath.slice(wsPath.lastIndexOf("/") + 1);
2875
+ const wsRelsXml = readXmlEntry(entries, `${dir}/_rels/${file}.rels`);
2876
+ if (wsRelsXml) {
2877
+ ws.loadPreservedState(wsXml, parseRelationships(wsRelsXml, wsPath));
2878
+ }
2879
+ }
2538
2880
  __privateGet(wb, _sheets).push({ worksheet: ws, sheetId, relId });
2539
2881
  }
2882
+ __privateSet(wb, _preservedParts, collectPreservedParts(entries));
2883
+ const ctXml = readXmlEntry(entries, "[Content_Types].xml");
2884
+ if (ctXml) __privateSet(wb, _preservedContentTypes, parseContentTypes(ctXml));
2540
2885
  for (const dn of getElementsByTagName(wbRoot, "definedName")) {
2541
2886
  const name = getAttr(dn, "name");
2542
2887
  if (!name) continue;
@@ -2586,6 +2931,30 @@ var _Workbook = class _Workbook {
2586
2931
  __privateGet(this, _sheets).push({ worksheet: ws, sheetId, relId });
2587
2932
  return ws;
2588
2933
  }
2934
+ /**
2935
+ * Removes the worksheet with the given name, along with any defined names
2936
+ * scoped to it. Throws if the sheet does not exist or is the last remaining
2937
+ * one — a workbook must always keep at least one sheet.
2938
+ *
2939
+ * Sheet ids and relationship ids are re-derived from document order on save,
2940
+ * so the remaining sheets stay consistent after a removal.
2941
+ */
2942
+ removeWorksheet(name) {
2943
+ __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
2944
+ const idx = __privateGet(this, _sheets).findIndex((e) => e.worksheet.name === name);
2945
+ if (idx < 0) throw new Error(`Worksheet "${name}" not found.`);
2946
+ if (__privateGet(this, _sheets).length === 1) {
2947
+ throw new Error("Cannot remove the last worksheet; a workbook must have at least one.");
2948
+ }
2949
+ const removed = __privateGet(this, _sheets)[idx].worksheet;
2950
+ for (const rel of removed.preservedRelationships) {
2951
+ if (rel.resolved) __privateGet(this, _orphanedTargets).push(rel.resolved);
2952
+ }
2953
+ __privateGet(this, _sheets).splice(idx, 1);
2954
+ for (const dn of [...__privateGet(this, _definedNames).all]) {
2955
+ if (dn.scope === name) __privateGet(this, _definedNames).remove(dn.name, dn.scope);
2956
+ }
2957
+ }
2589
2958
  /**
2590
2959
  * Returns a worksheet by name. Throws if not found.
2591
2960
  */
@@ -2670,19 +3039,72 @@ _styles3 = new WeakMap();
2670
3039
  _sheets = new WeakMap();
2671
3040
  _definedNames = new WeakMap();
2672
3041
  _closed = new WeakMap();
3042
+ _preservedParts = new WeakMap();
3043
+ _preservedContentTypes = new WeakMap();
3044
+ _orphanedTargets = new WeakMap();
2673
3045
  _Workbook_instances = new WeakSet();
2674
3046
  // ── Private helpers ────────────────────────────────────────────────────────
2675
3047
  assertOpen_fn = function() {
2676
3048
  if (__privateGet(this, _closed)) throw new Error("Workbook has been closed.");
2677
3049
  };
3050
+ /**
3051
+ * Copies preserved source parts into the output, minus those that are now
3052
+ * orphaned. A part is dropped when the only relationship pointing at it has
3053
+ * gone (a drawing replaced by live providers, a sheet removed, an explicit
3054
+ * `clearDrawings()`) and nothing else still reaches it.
3055
+ *
3056
+ * @returns the set of paths written, so later steps can avoid colliding with
3057
+ * them when naming newly generated parts.
3058
+ */
3059
+ seedPreservedParts_fn = function(entries) {
3060
+ if (__privateGet(this, _preservedParts).size === 0) return /* @__PURE__ */ new Set();
3061
+ const retained = /* @__PURE__ */ new Set();
3062
+ for (const { worksheet } of __privateGet(this, _sheets)) {
3063
+ for (const rel of worksheet.preservedRelationships) {
3064
+ if (rel.resolved) {
3065
+ for (const p of collectSubtree(__privateGet(this, _preservedParts), rel.resolved)) retained.add(p);
3066
+ }
3067
+ }
3068
+ }
3069
+ const candidates = [
3070
+ ...__privateGet(this, _sheets).flatMap((e) => [...e.worksheet.droppedPreservedTargets]),
3071
+ ...__privateGet(this, _orphanedTargets)
3072
+ ];
3073
+ const dropped = /* @__PURE__ */ new Set();
3074
+ for (const target of candidates) {
3075
+ for (const p of collectSubtree(__privateGet(this, _preservedParts), target)) {
3076
+ if (!retained.has(p)) dropped.add(p);
3077
+ }
3078
+ }
3079
+ const written = /* @__PURE__ */ new Set();
3080
+ for (const [path, bytes] of __privateGet(this, _preservedParts)) {
3081
+ if (dropped.has(path)) continue;
3082
+ entries.set(path, bytes);
3083
+ written.add(path);
3084
+ }
3085
+ return written;
3086
+ };
3087
+ /**
3088
+ * Content-type declarations for preserved parts. `<Default>` entries are kept
3089
+ * wholesale — they are extension-scoped and are what types binary parts such
3090
+ * as images — while `<Override>` entries are filtered to parts that survived.
3091
+ */
3092
+ preservedContentTypeDeclarations_fn = function(preservedPaths) {
3093
+ const ct = __privateGet(this, _preservedContentTypes);
3094
+ if (!ct) return { defaults: [], overrides: [] };
3095
+ const defaults = [...ct.defaults].filter(([ext]) => ext !== "rels" && ext !== "xml").map(([extension, contentType]) => ({ extension, contentType }));
3096
+ const overrides = [...ct.overrides].filter(([partName]) => preservedPaths.has(partName.replace(/^\//, ""))).map(([partName, contentType]) => ({ partName, contentType }));
3097
+ return { defaults, overrides };
3098
+ };
2678
3099
  buildZipEntries_fn = function() {
2679
3100
  const entries = /* @__PURE__ */ new Map();
2680
3101
  const sheetCount = __privateGet(this, _sheets).length;
3102
+ const preservedPaths = __privateMethod(this, _Workbook_instances, seedPreservedParts_fn).call(this, entries);
2681
3103
  entries.set("_rels/.rels", ROOT_RELS);
2682
- const sheetMetas = __privateGet(this, _sheets).map((e) => ({
3104
+ const sheetMetas = __privateGet(this, _sheets).map((e, i) => ({
2683
3105
  name: e.worksheet.name,
2684
- sheetId: e.sheetId,
2685
- relationshipId: e.relId,
3106
+ sheetId: i + 1,
3107
+ relationshipId: `rId${i + 1}`,
2686
3108
  state: e.worksheet.visibility !== "visible" ? e.worksheet.visibility : void 0
2687
3109
  }));
2688
3110
  const definedNameMetas = __privateGet(this, _definedNames).all.map((dn) => {
@@ -2705,34 +3127,48 @@ buildZipEntries_fn = function() {
2705
3127
  let partCounter = 0;
2706
3128
  for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
2707
3129
  const ws = __privateGet(this, _sheets)[i].worksheet;
2708
- if (!ws.hasDrawings) continue;
2709
- drawingCounter++;
2710
- const drawingPath = `xl/drawings/drawing${drawingCounter}.xml`;
2711
- const anchors = [];
2712
- const drawingRels = [];
2713
- ws.drawingProviders.forEach((provider, idx) => {
2714
- const relId = `rId${idx + 1}`;
2715
- anchors.push(provider.buildAnchorXml(relId));
2716
- partCounter++;
2717
- const part = provider.buildPart(partCounter);
2718
- entries.set(part.path, part.content);
2719
- extraOverrides.push({ partName: `/${part.path}`, contentType: part.contentType });
2720
- const target = `../${part.path.slice("xl/".length)}`;
2721
- drawingRels.push(
2722
- ` <Relationship Id="${relId}" Type="${part.relType}" Target="${target}"/>`
2723
- );
2724
- });
2725
- entries.set(drawingPath, buildDrawingXml(anchors.join("")));
2726
- entries.set(`xl/drawings/_rels/drawing${drawingCounter}.xml.rels`, buildRelsXml(drawingRels));
2727
- extraOverrides.push({ partName: `/${drawingPath}`, contentType: CONTENT_TYPES.drawing });
2728
- entries.set(
2729
- `xl/worksheets/_rels/sheet${i + 1}.xml.rels`,
2730
- buildRelsXml([
2731
- ` <Relationship Id="${WORKSHEET_DRAWING_REL_ID}" Type="${REL_TYPES.drawing}" Target="../drawings/drawing${drawingCounter}.xml"/>`
2732
- ])
3130
+ const sheetRels = ws.preservedRelationships.map(
3131
+ (r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${r.target}"${r.external ? ' TargetMode="External"' : ""}/>`
2733
3132
  );
3133
+ if (ws.hasDrawings) {
3134
+ do {
3135
+ drawingCounter++;
3136
+ } while (preservedPaths.has(`xl/drawings/drawing${drawingCounter}.xml`));
3137
+ const drawingPath = `xl/drawings/drawing${drawingCounter}.xml`;
3138
+ const anchors = [];
3139
+ const drawingRels = [];
3140
+ ws.drawingProviders.forEach((provider, idx) => {
3141
+ const relId = `rId${idx + 1}`;
3142
+ anchors.push(provider.buildAnchorXml(relId));
3143
+ let part = provider.buildPart(++partCounter);
3144
+ while (preservedPaths.has(part.path)) part = provider.buildPart(++partCounter);
3145
+ entries.set(part.path, part.content);
3146
+ extraOverrides.push({ partName: `/${part.path}`, contentType: part.contentType });
3147
+ const target = `../${part.path.slice("xl/".length)}`;
3148
+ drawingRels.push(
3149
+ ` <Relationship Id="${relId}" Type="${part.relType}" Target="${target}"/>`
3150
+ );
3151
+ });
3152
+ entries.set(drawingPath, buildDrawingXml(anchors.join("")));
3153
+ entries.set(`xl/drawings/_rels/drawing${drawingCounter}.xml.rels`, buildRelsXml(drawingRels));
3154
+ extraOverrides.push({ partName: `/${drawingPath}`, contentType: CONTENT_TYPES.drawing });
3155
+ sheetRels.push(
3156
+ ` <Relationship Id="${ws.drawingRelId}" Type="${REL_TYPES.drawing}" Target="../drawings/drawing${drawingCounter}.xml"/>`
3157
+ );
3158
+ }
3159
+ if (sheetRels.length > 0) {
3160
+ entries.set(`xl/worksheets/_rels/sheet${i + 1}.xml.rels`, buildRelsXml(sheetRels));
3161
+ }
2734
3162
  }
2735
- entries.set("[Content_Types].xml", buildContentTypes(sheetCount, extraOverrides));
3163
+ const preservedCt = __privateMethod(this, _Workbook_instances, preservedContentTypeDeclarations_fn).call(this, preservedPaths);
3164
+ entries.set(
3165
+ "[Content_Types].xml",
3166
+ buildContentTypes(
3167
+ sheetCount,
3168
+ [...preservedCt.overrides, ...extraOverrides],
3169
+ preservedCt.defaults
3170
+ )
3171
+ );
2736
3172
  return entries;
2737
3173
  };
2738
3174
  var Workbook = _Workbook;
@@ -2785,6 +3221,7 @@ exports.columnNumber = columnNumber;
2785
3221
  exports.decodeUtf8 = decodeUtf8;
2786
3222
  exports.encodeUtf8 = encodeUtf8;
2787
3223
  exports.escapeXml = escapeXml;
3224
+ exports.formatNumberWithCode = formatNumberWithCode;
2788
3225
  exports.fromOADate = fromOADate;
2789
3226
  exports.getAttr = getAttr;
2790
3227
  exports.getElementsByTagName = getElementsByTagName;