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