xlvane 1.0.0 → 1.1.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.
@@ -1,4 +1,4 @@
1
- import { $t as MAX_COLUMNS, At as mergeStyles, B as tokenize, Dt as parseStyles, Et as EMPTY_STYLES, Ft as resolveLimits, I as WarningCollector, It as suggest, M as openZip, Mt as stableKey, Nt as isDateFormat, Ot as READING_ORDERS, Pt as DEFAULT_READ_LIMITS, Qt as relationshipTypes, Sn as XlvError, U as attr, V as DEFAULT_FONT, W as borderXml, X as fontXml, Xt as SML_NS, Y as fillXml, Z as formatNumber, Zt as classifyRelationship, _ as isoDate, _n as encodeXstring, _t as parseSharedStrings, an as parseRange, b as readWorkbook, bt as partKey, ct as stringItemXml, dn as childrenNamed, en as MAX_ROWS, f as createWorkbook, fn as parseXmlTree, gn as parseXml, gt as parseIsoDateTime, h as detectHeader, hn as normalizeLineEnds, i as readOptions, in as parseCellRef, jt as normalizeStyle, kt as checkStyleCount, m as writeWorkbook, mn as decodeXml, mt as writeZip, n as oneOfOption, nn as formatCellRef, r as openInput, rn as formatRange, t as booleanOption, tn as columnLetters, tt as reusableBuiltinNumFmt, un as childNamed, ut as textElement, vn as escapeAttr, vt as openPackage, x as toBytes, xt as zipNameToPartName, yn as escapeText, yt as relationshipsPartName, z as shiftFormula } from "./options-B7UhudO_.js";
1
+ import { $ as fillXml, At as EMPTY_STYLES, B as tokenize, Bt as suggest, Cn as escapeText, Ct as relationshipsPartName, En as XlvError, Ft as normalizeStyle, G as DEFAULT_FONT, H as X14_DV_URI, I as WarningCollector, It as stableKey, J as borderXml, Lt as isDateFormat, M as openZip, Mt as READING_ORDERS, Nt as checkStyleCount, Pt as mergeStyles, Rt as DEFAULT_READ_LIMITS, Sn as escapeAttr, St as openPackage, Tt as zipNameToPartName, V as X14_CF_URI, _ as isoDate, an as columnLetters, at as reusableBuiltinNumFmt, b as readWorkbook, bn as parseXml, bt as parseIsoDateTime, cn as parseCellRef, en as SML_NS, et as fontXml, f as createWorkbook, ft as stringItemXml, gn as parseXmlTree, h as detectHeader, hn as childrenNamed, i as readOptions, in as MAX_ROWS, jt as parseStyles, ln as parseRange, m as writeWorkbook, mn as childNamed, mt as textElement, n as oneOfOption, nn as relationshipTypes, on as formatCellRef, q as attr, r as openInput, rn as MAX_COLUMNS, sn as formatRange, t as booleanOption, tn as classifyRelationship, tt as formatNumber, vn as decodeXml, vt as writeZip, wt as partKey, x as toBytes, xn as encodeXstring, xt as parseSharedStrings, yn as normalizeLineEnds, z as shiftFormula, zt as resolveLimits } from "./options-CwSp2pA4.js";
2
2
  // src/table.ts
3
3
  const READ_OPTIONS = [
4
4
  "sheet",
@@ -1030,7 +1030,7 @@ function withPrefix(xml, prefix) {
1030
1030
  /** Sets (or adds) an attribute in a start tag given as source text. */
1031
1031
  function setAttr(tag, name, value) {
1032
1032
  const pattern = new RegExp(`(\\s${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*)("[^"]*"|'[^']*')`);
1033
- if (pattern.test(tag)) return tag.replace(pattern, `$1"${escapeAttr(value)}"`);
1033
+ if (pattern.test(tag)) return tag.replace(pattern, (_m, head) => `${head}"${escapeAttr(value)}"`);
1034
1034
  const close = tag.endsWith("/>") ? tag.length - 2 : tag.length - 1;
1035
1035
  return `${tag.slice(0, close)} ${name}="${escapeAttr(value)}"${tag.slice(close)}`;
1036
1036
  }
@@ -1078,14 +1078,15 @@ function scan(text, part, limits, keep) {
1078
1078
  const names = [];
1079
1079
  let root = null;
1080
1080
  parseXml(text, {
1081
- start(_ns, local, attrs, span) {
1081
+ start(ns, local, attrs, span) {
1082
1082
  const depth = names.length + 1;
1083
1083
  const parent = names[names.length - 1] ?? "";
1084
1084
  names.push(local);
1085
1085
  let element = null;
1086
- if (depth === 1 || keep(local, depth, parent)) {
1086
+ if (depth === 1 || keep(local, depth, parent, ns)) {
1087
1087
  element = {
1088
1088
  local,
1089
+ ns,
1089
1090
  depth,
1090
1091
  attrs,
1091
1092
  start: span,
@@ -2649,10 +2650,27 @@ function notSupported(message, location, details) {
2649
2650
  function adjustText(raw, c, formulaSheet) {
2650
2651
  if (raw.includes("<")) return null;
2651
2652
  const text = decodeEntities(raw);
2652
- const out = adjustFormula(text, refChange(c, formulaSheet));
2653
- if (out === null) return formulaSheet.toLowerCase() !== c.sheet.toLowerCase() && !text.includes("!") ? raw : null;
2653
+ const out = adjustDecoded(text, c, formulaSheet);
2654
+ if (out === null) return null;
2654
2655
  return out === text ? raw : escapeText(out);
2655
2656
  }
2657
+ /** {@link adjustText} for formula text that is already decoded, such as an attribute value. */
2658
+ function adjustDecoded(text, c, formulaSheet) {
2659
+ const out = adjustFormula(text, refChange(c, formulaSheet));
2660
+ if (out === null) return formulaSheet.toLowerCase() !== c.sheet.toLowerCase() && !text.includes("!") ? text : null;
2661
+ return out;
2662
+ }
2663
+ /** `ext@uri` values of the worksheet extensions whose ranges and formulas are rewritten (SR-40, D-F04). */
2664
+ const KNOWN_EXTENSIONS = new Set([X14_CF_URI, X14_DV_URI].map((u) => u.toLowerCase()));
2665
+ const isKnownExtension = (e) => KNOWN_EXTENSIONS.has((e.attrs.get("uri") ?? "").trim().toLowerCase());
2666
+ /** Start tags named `f` or `sqref`, with any prefix: the formulas and ranges an extension can hold. */
2667
+ const FORMULA_OR_RANGE_TAG = /<(?:[\w.-]+:)?(?:f|sqref)[\s/>]/g;
2668
+ const CFVO_PARENTS = /* @__PURE__ */ new Set([
2669
+ "colorScale",
2670
+ "dataBar",
2671
+ "iconSet"
2672
+ ]);
2673
+ const within = (outer, inner) => inner.start.start >= outer.start.start && inner.end.end <= outer.end.end;
2656
2674
  /**
2657
2675
  * Applies non-overlapping replacements in one pass (linear in the text size, unlike edit.ts
2658
2676
  * `splice`, which matters for sheets with many moved cells). An edit inside an earlier replaced
@@ -2683,7 +2701,7 @@ const KEEP_DEPTH3 = /* @__PURE__ */ new Set([
2683
2701
  ]);
2684
2702
  /** Rewrites one worksheet part: references everywhere, and row positions on the changed sheet. */
2685
2703
  function rewriteSheet(text, part, sheet, c, target, style, limits) {
2686
- const { elements } = scan(text, part, limits, (local, depth, parent) => depth === 2 || depth === 3 && KEEP_DEPTH3.has(local) || depth === 4 && (local === "c" && parent === "row" || local === "formula" && parent === "cfRule" || (local === "formula1" || local === "formula2") && parent === "dataValidation") || depth === 5 && local === "f" && parent === "c");
2704
+ const { elements } = scan(text, part, limits, (local, depth, parent, ns) => depth === 2 || depth === 3 && (KEEP_DEPTH3.has(local) || local === "ext" && parent === "extLst") || depth === 4 && (local === "c" && parent === "row" || local === "formula" && parent === "cfRule" || (local === "formula1" || local === "formula2") && parent === "dataValidation") || depth === 5 && (local === "f" && parent === "c" || local === "cfvo" && CFVO_PARENTS.has(parent)) || ns === "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" && (depth === 4 && (local === "conditionalFormattings" || local === "dataValidations") || depth === 5 && (local === "conditionalFormatting" || local === "dataValidation") || depth === 6 && (local === "cfRule" || local === "id" && parent === "ext")) || ns === "http://schemas.microsoft.com/office/excel/2006/main" && (depth === 6 && local === "sqref" || depth >= 7 && local === "f"));
2687
2705
  const edits = [];
2688
2706
  const tagOf = (e) => text.slice(e.start.start, e.start.end);
2689
2707
  const replaceTag = (e, tag) => {
@@ -2712,11 +2730,26 @@ function rewriteSheet(text, part, sheet, c, target, style, limits) {
2712
2730
  if (e.depth !== 2) continue;
2713
2731
  const what = REFUSED_ELEMENTS[e.local];
2714
2732
  if (what !== void 0) throw notSupported(`Sheet "${sheet.name}" has ${what}, which xlvane cannot move with its rows yet.`, where(), { element: e.local });
2715
- if (e.local === "extLst" && /sqref|<xm:f|sparkline|slicer/i.test(text.slice(e.start.start, e.end.end))) throw notSupported(`Sheet "${sheet.name}" has extended content (such as data bars, sparklines or slicers) that refers to cells, which xlvane cannot move with its rows yet.`, where(), { element: "extLst" });
2716
2733
  }
2717
- else {
2718
- const ext = elements.find((e) => e.depth === 2 && e.local === "extLst");
2719
- if (ext && /<xm:f|sparkline/i.test(text.slice(ext.start.start, ext.end.end)) && text.slice(ext.start.start, ext.end.end).toLowerCase().includes(c.sheet.toLowerCase())) throw notSupported(`Sheet "${sheet.name}" has extended content (such as sparklines) that may refer to sheet "${c.sheet}", which xlvane cannot rewrite yet.`, where(), { element: "extLst" });
2734
+ const knownExts = /* @__PURE__ */ new Set();
2735
+ {
2736
+ let current = null;
2737
+ let found = 0;
2738
+ const close = () => {
2739
+ if (current !== null && (text.slice(current.start.start, current.end.end).match(FORMULA_OR_RANGE_TAG) ?? []).length === found) knownExts.add(current);
2740
+ };
2741
+ for (const e of elements) if (e.depth === 3 && e.local === "ext") {
2742
+ close();
2743
+ current = isKnownExtension(e) ? e : null;
2744
+ found = 0;
2745
+ } else if (current !== null && e.ns === "http://schemas.microsoft.com/office/excel/2006/main" && (e.depth === 6 && e.local === "sqref" || e.depth >= 7 && e.local === "f") && within(current, e)) found++;
2746
+ close();
2747
+ }
2748
+ for (const ext of elements) {
2749
+ if (ext.depth !== 3 || ext.local !== "ext" || knownExts.has(ext)) continue;
2750
+ const body = text.slice(ext.start.start, ext.end.end);
2751
+ if (target && /sqref|<xm:f|sparkline|slicer/i.test(body)) throw notSupported(`Sheet "${sheet.name}" has extended content (such as sparklines or slicers) that refers to cells, which xlvane cannot move with its rows yet.`, where(), { element: "extLst" });
2752
+ if (!target && /<xm:f|sparkline/i.test(body) && body.toLowerCase().includes(c.sheet.toLowerCase())) throw notSupported(`Sheet "${sheet.name}" has extended content (such as sparklines) that may refer to sheet "${c.sheet}", which xlvane cannot rewrite yet.`, where(), { element: "extLst" });
2720
2753
  }
2721
2754
  const rows = [];
2722
2755
  let current = null;
@@ -2732,64 +2765,83 @@ function rewriteSheet(text, part, sheet, c, target, style, limits) {
2732
2765
  });
2733
2766
  };
2734
2767
  let hasFormulas = false;
2735
- for (const e of elements) if (e.depth === 3 && e.local === "row") {
2736
- const r = e.attrs.get("r");
2737
- const n = r === void 0 ? currentRow + 1 : /^[0-9]{1,7}$/.test(r.trim()) ? Number(r.trim()) : NaN;
2738
- if (!Number.isInteger(n) || n < 1 || n > 1048576) invalidRef(r ?? String(n));
2739
- currentRow = n;
2740
- current = {
2741
- number: n,
2742
- hasR: r !== void 0,
2743
- element: e,
2744
- cells: []
2745
- };
2746
- rows.push(current);
2747
- lastCol = 0;
2748
- } else if (e.depth === 4 && e.local === "c" && current) {
2749
- const r = e.attrs.get("r");
2750
- const address = r === void 0 || r === "" ? null : parseCellRef(r);
2751
- if (r !== void 0 && r !== "" && address === null) invalidRef(r);
2752
- const col = address?.col ?? lastCol + 1;
2753
- lastCol = col;
2754
- lastCell = {
2755
- col,
2756
- hasR: r !== void 0 && r !== "",
2757
- element: e,
2758
- f: null
2759
- };
2760
- current.cells.push(lastCell);
2761
- } else if (e.depth === 5 && e.local === "f" && lastCell && current) {
2762
- lastCell.f = e;
2763
- hasFormulas = true;
2764
- formulas.push({
2765
- cell: lastCell,
2766
- row: current
2767
- });
2768
- } else if (e.depth === 4 && (e.local === "formula" || e.local === "formula1" || e.local === "formula2")) {
2769
- const raw = content(text, e);
2770
- const out = adjustText(raw, c, sheet.name);
2771
- if (out === null) throw notSupported(`A ${e.local === "formula" ? "conditional format" : "data validation"} formula of sheet "${sheet.name}" cannot be adjusted safely: "${decodeEntities(raw).slice(0, 60)}".`, where(), { formula: decodeEntities(raw).slice(0, 80) });
2772
- if (out !== raw) edits.push([
2773
- e.start.end,
2774
- e.end.start,
2775
- out
2776
- ]);
2777
- } else if (e.depth === 3 && e.local === "hyperlink") {
2778
- const location = e.attrs.get("location");
2779
- let tag = tagOf(e);
2780
- if (location !== void 0) {
2781
- const out = adjustFormula(location, refChange(c, sheet.name));
2782
- if (out !== null && out !== location) tag = setAttr(tag, "location", out);
2783
- }
2784
- if (target) {
2785
- const ref = adjustRange(e.attrs.get("ref") ?? "", refChange(c, sheet.name));
2786
- if (ref === "") {
2787
- removeElement(e);
2788
- continue;
2768
+ let extension = null;
2769
+ for (const e of elements) {
2770
+ if (e.depth === 3 && e.local === "ext") extension = knownExts.has(e) ? e : null;
2771
+ if (e.depth === 3 && e.local === "row") {
2772
+ const r = e.attrs.get("r");
2773
+ const n = r === void 0 ? currentRow + 1 : /^[0-9]{1,7}$/.test(r.trim()) ? Number(r.trim()) : NaN;
2774
+ if (!Number.isInteger(n) || n < 1 || n > 1048576) invalidRef(r ?? String(n));
2775
+ currentRow = n;
2776
+ current = {
2777
+ number: n,
2778
+ hasR: r !== void 0,
2779
+ element: e,
2780
+ cells: []
2781
+ };
2782
+ rows.push(current);
2783
+ lastCol = 0;
2784
+ } else if (e.depth === 4 && e.local === "c" && current) {
2785
+ const r = e.attrs.get("r");
2786
+ const address = r === void 0 || r === "" ? null : parseCellRef(r);
2787
+ if (r !== void 0 && r !== "" && address === null) invalidRef(r);
2788
+ const col = address?.col ?? lastCol + 1;
2789
+ lastCol = col;
2790
+ lastCell = {
2791
+ col,
2792
+ hasR: r !== void 0 && r !== "",
2793
+ element: e,
2794
+ f: null
2795
+ };
2796
+ current.cells.push(lastCell);
2797
+ } else if (e.depth === 5 && e.local === "f" && lastCell && current) {
2798
+ lastCell.f = e;
2799
+ hasFormulas = true;
2800
+ formulas.push({
2801
+ cell: lastCell,
2802
+ row: current
2803
+ });
2804
+ } else if (e.depth === 4 && (e.local === "formula" || e.local === "formula1" || e.local === "formula2")) {
2805
+ const raw = content(text, e);
2806
+ const out = adjustText(raw, c, sheet.name);
2807
+ if (out === null) throw notSupported(`A ${e.local === "formula" ? "conditional format" : "data validation"} formula of sheet "${sheet.name}" cannot be adjusted safely: "${decodeEntities(raw).slice(0, 60)}".`, where(), { formula: decodeEntities(raw).slice(0, 80) });
2808
+ if (out !== raw) edits.push([
2809
+ e.start.end,
2810
+ e.end.start,
2811
+ out
2812
+ ]);
2813
+ } else if (e.depth === 5 && e.local === "cfvo") {
2814
+ const val = e.attrs.get("val");
2815
+ if (val === void 0) continue;
2816
+ const out = adjustDecoded(val, c, sheet.name);
2817
+ if (out === null) throw notSupported(`A conditional format threshold of sheet "${sheet.name}" cannot be adjusted safely: "${val.slice(0, 60)}".`, where(), { formula: val.slice(0, 80) });
2818
+ if (out !== val) replaceTag(e, setAttr(tagOf(e), "val", out));
2819
+ } else if (e.ns === "http://schemas.microsoft.com/office/excel/2006/main" && e.local === "f" && extension !== null && within(extension, e)) {
2820
+ const raw = content(text, e);
2821
+ const out = adjustText(raw, c, sheet.name);
2822
+ if (out === null) throw notSupported(`A ${(extension.attrs.get("uri") ?? "").toLowerCase() === "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}".toLowerCase() ? "data validation" : "conditional format"} formula of sheet "${sheet.name}" cannot be adjusted safely: "${decodeEntities(raw).slice(0, 60)}".`, where(), { formula: decodeEntities(raw).slice(0, 80) });
2823
+ if (out !== raw) edits.push([
2824
+ e.start.end,
2825
+ e.end.start,
2826
+ out
2827
+ ]);
2828
+ } else if (e.depth === 3 && e.local === "hyperlink") {
2829
+ const location = e.attrs.get("location");
2830
+ let tag = tagOf(e);
2831
+ if (location !== void 0) {
2832
+ const out = adjustFormula(location, refChange(c, sheet.name));
2833
+ if (out !== null && out !== location) tag = setAttr(tag, "location", out);
2789
2834
  }
2790
- if (ref !== null && ref !== e.attrs.get("ref")) tag = setAttr(tag, "ref", ref);
2835
+ if (target) {
2836
+ const ref = adjustRange(e.attrs.get("ref") ?? "", refChange(c, sheet.name));
2837
+ if (ref === "") {
2838
+ removeElement(e);
2839
+ continue;
2840
+ }
2841
+ if (ref !== null && ref !== e.attrs.get("ref")) tag = setAttr(tag, "ref", ref);
2842
+ }
2843
+ if (tag !== tagOf(e)) replaceTag(e, tag);
2791
2844
  }
2792
- if (tag !== tagOf(e)) replaceTag(e, tag);
2793
2845
  }
2794
2846
  const masters = /* @__PURE__ */ new Map();
2795
2847
  for (const { cell, row } of formulas) {
@@ -2917,6 +2969,15 @@ function rewriteSheet(text, part, sheet, c, target, style, limits) {
2917
2969
  }
2918
2970
  }
2919
2971
  }
2972
+ const removedIds = /* @__PURE__ */ new Set();
2973
+ const linkIds = /* @__PURE__ */ new Map();
2974
+ let format = null;
2975
+ for (const e of elements) if (e.depth === 2) format = e.local === "conditionalFormatting" ? e : null;
2976
+ else if (format !== null && e.depth === 6 && e.ns === "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" && e.local === "id") {
2977
+ const list = linkIds.get(format) ?? [];
2978
+ list.push(content(text, e).trim().toLowerCase());
2979
+ linkIds.set(format, list);
2980
+ }
2920
2981
  const containers = /* @__PURE__ */ new Map();
2921
2982
  let parent2 = null;
2922
2983
  for (const e of elements) {
@@ -2934,8 +2995,10 @@ function rewriteSheet(text, part, sheet, c, target, style, limits) {
2934
2995
  const sqref = e.attrs.get("sqref") ?? "";
2935
2996
  const out = adjustRange(sqref, refChange(c, sheet.name));
2936
2997
  if (out === null) throw notSupported(`A conditional format of sheet "${sheet.name}" has a range xlvane cannot adjust: "${sqref.slice(0, 60)}".`, where());
2937
- if (out === "") removeElement(e);
2938
- else if (out !== sqref) replaceTag(e, setAttr(tagOf(e), "sqref", out));
2998
+ if (out === "") {
2999
+ removeElement(e);
3000
+ for (const id of linkIds.get(e) ?? []) removedIds.add(id);
3001
+ } else if (out !== sqref) replaceTag(e, setAttr(tagOf(e), "sqref", out));
2939
3002
  }
2940
3003
  continue;
2941
3004
  }
@@ -3007,6 +3070,7 @@ function rewriteSheet(text, part, sheet, c, target, style, limits) {
3007
3070
  if (tag !== tagOf(container)) replaceTag(container, tag);
3008
3071
  }
3009
3072
  }
3073
+ rewriteExtensionRanges(text, elements, knownExts, c, sheet, removedIds, edits, where);
3010
3074
  const result = applyEdits(text, edits);
3011
3075
  return {
3012
3076
  text: /sortState/.test(result) ? rewriteSortStates(result, part, sheet, c, limits) : result,
@@ -3014,6 +3078,90 @@ function rewriteSheet(text, part, sheet, c, target, style, limits) {
3014
3078
  };
3015
3079
  }
3016
3080
  /**
3081
+ * Moves the ranges (`xm:sqref`) of the extension conditional formats and data validations of the
3082
+ * changed sheet (SR-40, SR-44; R-20; D-F04). An item whose cells are all deleted is removed, as is
3083
+ * an extension rule linked to a removed classic rule (SR-41); emptied containers, `ext` and
3084
+ * `extLst` elements are removed, and `x14:dataValidations@count` is updated.
3085
+ */
3086
+ function rewriteExtensionRanges(text, elements, knownExts, c, sheet, removedIds, edits, where) {
3087
+ const roots = [];
3088
+ const stack = [];
3089
+ for (const e of elements) {
3090
+ if (!(e.depth === 2 && e.local === "extLst" || e.depth === 3 && e.local === "ext" || e.ns === "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" && e.depth >= 4 && e.depth <= 6 || e.ns === "http://schemas.microsoft.com/office/excel/2006/main" && e.depth === 6 && e.local === "sqref")) continue;
3091
+ while (stack.length > 0 && !within(stack[stack.length - 1].e, e)) stack.pop();
3092
+ const node = {
3093
+ e,
3094
+ children: []
3095
+ };
3096
+ (stack.length > 0 ? stack[stack.length - 1].children : roots).push(node);
3097
+ stack.push(node);
3098
+ }
3099
+ const tagOf = (e) => text.slice(e.start.start, e.start.end);
3100
+ const remove = (e) => {
3101
+ edits.push([
3102
+ e.start.start,
3103
+ e.end.end,
3104
+ ""
3105
+ ]);
3106
+ };
3107
+ for (const list of roots) {
3108
+ if (list.e.local !== "extLst") continue;
3109
+ let extsKept = 0;
3110
+ for (const ext of list.children) {
3111
+ if (ext.e.local !== "ext" || !knownExts.has(ext.e)) {
3112
+ extsKept++;
3113
+ continue;
3114
+ }
3115
+ let containersKept = 0;
3116
+ let containersTotal = 0;
3117
+ for (const container of ext.children) {
3118
+ if (container.e.depth !== 4 || container.e.ns !== "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") continue;
3119
+ containersTotal++;
3120
+ let kept = 0;
3121
+ let total = 0;
3122
+ for (const item of container.children) {
3123
+ if (item.e.depth !== 5 || item.e.ns !== "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") continue;
3124
+ total++;
3125
+ let gone = false;
3126
+ const sqref = item.children.find((n) => n.e.ns === "http://schemas.microsoft.com/office/excel/2006/main" && n.e.local === "sqref")?.e;
3127
+ if (sqref) {
3128
+ const normal = decodeEntities(content(text, sqref)).trim().split(/\s+/).join(" ");
3129
+ const out = normal === "" ? "" : adjustRange(normal, refChange(c, sheet.name));
3130
+ if (out === null) throw notSupported(`An extension ${item.e.local === "dataValidation" ? "data validation" : "conditional format"} of sheet "${sheet.name}" has a range xlvane cannot adjust: "${normal.slice(0, 60)}".`, where());
3131
+ if (out === "" && normal !== "") gone = true;
3132
+ else if (out !== normal) edits.push([
3133
+ sqref.start.end,
3134
+ sqref.end.start,
3135
+ escapeText(out)
3136
+ ]);
3137
+ }
3138
+ if (!gone && removedIds.size > 0) {
3139
+ const rules = item.children.filter((n) => n.e.ns === "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" && n.e.local === "cfRule").map((n) => n.e);
3140
+ const linked = rules.filter((r) => removedIds.has((r.attrs.get("id") ?? "").trim().toLowerCase()));
3141
+ if (linked.length > 0 && linked.length === rules.length) gone = true;
3142
+ else for (const r of linked) remove(r);
3143
+ }
3144
+ if (gone) remove(item.e);
3145
+ else kept++;
3146
+ }
3147
+ if (kept === 0 && total > 0) {
3148
+ remove(container.e);
3149
+ continue;
3150
+ }
3151
+ containersKept++;
3152
+ if (kept < total && container.e.attrs.has("count")) edits.push([
3153
+ container.e.start.start,
3154
+ container.e.start.end,
3155
+ setAttr(tagOf(container.e), "count", String(kept))
3156
+ ]);
3157
+ }
3158
+ if (containersTotal > 0 && containersKept === 0) remove(ext.e);
3159
+ else extsKept++;
3160
+ }
3161
+ if (list.children.length > 0 && extsKept === 0) remove(list.e);
3162
+ }
3163
+ }
3164
+ /**
3017
3165
  * Moves the sort ranges of the changed sheet (`sortState` at sheet level or inside `autoFilter`,
3018
3166
  * and their `sortCondition`s) as ranges move (D-B01). A sort range that is deleted entirely is
3019
3167
  * removed; one that cannot be adjusted is refused. Runs on the text after the other edits, so
@@ -3449,15 +3597,14 @@ function changeRows(input, sheetName, at, count, style, limits) {
3449
3597
  * Inserts empty rows into a sheet of an existing XLSX file and returns the new file. Rows below
3450
3598
  * move down, and every reference follows them as in a spreadsheet application: formulas on all
3451
3599
  * sheets (ranges that the new rows fall into grow), merged cells, hyperlinks, notes, the
3452
- * autofilter, conditional formats, data validation, page breaks, defined names, tables (which
3453
- * grow when rows are inserted inside them) and pictures placed on the sheet. Every other part of
3454
- * the file stays exactly as it was.
3600
+ * autofilter, conditional formats (also those of the [MS-XLSX] extension, and thresholds that
3601
+ * refer to cells), data validation (also of the extension), page breaks, defined names, tables
3602
+ * (which grow when rows are inserted inside them) and pictures placed on the sheet. Every other
3603
+ * part of the file stays exactly as it was.
3455
3604
  *
3456
3605
  * Refused with `XLV_E_NOT_SUPPORTED`, leaving the file unchanged: a drawing that holds charts,
3457
3606
  * shapes or anything besides pictures; embedded objects, form controls, scenarios, cell watches,
3458
- * custom sheet views, extended content that refers to cells (data bars, sparklines, slicers -
3459
- * including the extended data bar settings and the icon sets `3Stars`, `3Triangles`, `5Boxes`, `NoIcons`
3460
- * that xlvane itself writes) and
3607
+ * custom sheet views, other extended content that refers to cells (sparklines, slicers) and
3461
3608
  * other related parts xlvane does not know; charts or pivot tables anywhere in the workbook that
3462
3609
  * may refer to the sheet; data tables; an array formula the change would split; and formulas it
3463
3610
  * cannot rewrite safely (docs/facts/ref-adjust.md).
@@ -1,4 +1,4 @@
1
- import { $ as nameTitles, $t as MAX_COLUMNS, A as limitExceeded, C as Bytes, Ct as normalizeTable, D as TAIL_BYTES, E as NotLoaded, F as validateLocalHeaders, Ft as resolveLimits, G as buildStyles, Gt as MediaParts, H as SheetRels, I as WarningCollector, J as createSharedStrings, Jt as metricsOf, K as cellXml, Kt as drawingParts, L as sheetNotFound, Lt as printNames, N as readCentralDirectory, O as checkSource$1, P as unsupported, Pt as DEFAULT_READ_LIMITS, Q as hyperlinksXml, R as createWorksheetParser, Rt as printXml, S as workbookPartsToLoad, Sn as XlvError, St as TABLE_CONTENT_TYPE, T as HEAD_BYTES, Tt as checkUniqueName, Ut as DRAWING_CONTENT_TYPE, Wt as IMAGE_CONTENT_TYPES, Yt as resolveImage, Zt as classifyRelationship, _ as isoDate, a as DATE_FORMAT, at as sheetRelsPartName, cn as appPropertiesXml, d as convertCellInput, dt as workbookRelsXml, en as MAX_ROWS, et as noteParts, f as createWorkbook, ft as workbookXml, h as detectHeader, ht as crc32, i as readOptions, it as sheetHead, j as locateCentralDirectory, jt as normalizeStyle, k as corrupt, ln as corePropertiesXml, lt as tableParts, n as oneOfOption, nn as formatCellRef, nt as rootRelsXml, o as DATE_TIME_FORMAT, on as APP_PROPERTIES_PART, ot as sheetRulesXml, p as sortedNotes, pn as createXmlByteStreamParser, pt as FormulaGroups, q as contentTypesXml, rt as rowStart, sn as CORE_PROPERTIES_PART, st as sheetTail, tn as columnLetters, u as checkBookViews, v as analyzeWorkbook, vt as openPackage, w as CORRUPT_HINT, wt as checkDefinedName, y as readNotes, zt as sheetProtectionXml } from "./options-B7UhudO_.js";
1
+ import { $t as resolveImage, A as limitExceeded, C as Bytes, D as TAIL_BYTES, Dt as normalizeTable, E as NotLoaded, En as XlvError, Et as TABLE_CONTENT_TYPE, F as validateLocalHeaders, Ft as normalizeStyle, Ht as printXml, I as WarningCollector, Jt as IMAGE_CONTENT_TYPES, K as SheetRels, L as sheetNotFound, N as readCentralDirectory, O as checkSource$1, Ot as checkDefinedName, P as unsupported, Q as createSharedStrings, Qt as metricsOf, R as createWorksheetParser, Rt as DEFAULT_READ_LIMITS, S as workbookPartsToLoad, St as openPackage, T as HEAD_BYTES, Ut as sheetProtectionXml, Vt as printNames, X as cellXml, Xt as drawingParts, Y as buildStyles, Yt as MediaParts, Z as contentTypesXml, _ as isoDate, _n as createXmlByteStreamParser, _t as FormulaGroups, a as DATE_FORMAT, an as columnLetters, ct as sheetHead, d as convertCellInput, dn as CORE_PROPERTIES_PART, dt as sheetTail, f as createWorkbook, fn as appPropertiesXml, gt as workbookXml, h as detectHeader, ht as workbookRelsXml, i as readOptions, in as MAX_ROWS, it as noteParts, j as locateCentralDirectory, k as corrupt, kt as checkUniqueName, lt as sheetRelsPartName, n as oneOfOption, nt as hyperlinksXml, o as DATE_TIME_FORMAT, on as formatCellRef, ot as rootRelsXml, p as sortedNotes, pn as corePropertiesXml, pt as tableParts, qt as DRAWING_CONTENT_TYPE, rn as MAX_COLUMNS, rt as nameTitles, st as rowStart, tn as classifyRelationship, u as checkBookViews, un as APP_PROPERTIES_PART, ut as sheetRulesXml, v as analyzeWorkbook, w as CORRUPT_HINT, y as readNotes, yt as crc32, zt as resolveLimits } from "./options-CwSp2pA4.js";
2
2
  import { Deflate, Inflate } from "fflate";
3
3
  // src/zip/stream.ts
4
4
  /**
@@ -571,6 +571,7 @@ async function openWorkbookStream(source, options) {
571
571
  sheets: info.sheets.map((s) => ({
572
572
  name: s.name,
573
573
  index: s.index,
574
+ sheetId: s.sheetId,
574
575
  kind: s.kind,
575
576
  state: s.state
576
577
  })),
@@ -850,6 +851,7 @@ function writeWorkbookStream(options) {
850
851
  const s = readOptions(raw, [
851
852
  "name",
852
853
  "state",
854
+ "sheetId",
853
855
  "columns",
854
856
  "freeze",
855
857
  "tabColor",
@@ -872,7 +874,11 @@ function writeWorkbookStream(options) {
872
874
  "rows"
873
875
  ], `sheets[${String(i)}]`);
874
876
  const state = s.get("state");
875
- const sheet = book.addSheet(s.get("name"), state === void 0 ? void 0 : { state });
877
+ const sheetId = s.get("sheetId");
878
+ const sheet = book.addSheet(s.get("name"), {
879
+ ...state === void 0 ? {} : { state },
880
+ ...sheetId === void 0 ? {} : { sheetId }
881
+ });
876
882
  const columns = s.get("columns");
877
883
  if (columns !== void 0) {
878
884
  if (!Array.isArray(columns)) throw new XlvError("XLV_E_INVALID_OPTION", `sheets[${String(i)}].columns must be an array.`, {
@@ -1188,6 +1194,7 @@ function writeWorkbookStream(options) {
1188
1194
  data: encoder.encode(workbookXml(prepared.map((p) => ({
1189
1195
  name: p.sheet.name,
1190
1196
  state: p.sheet.state,
1197
+ sheetId: p.sheet.sheetId,
1191
1198
  autoFilter: p.filterRange
1192
1199
  })), book.dateSystem, allNames(), book.workbookProtection, {
1193
1200
  bookViews: book.bookViews,
@@ -2413,13 +2413,19 @@ type SheetLayout = "normal" | "pageBreakPreview" | "pageLayout";
2413
2413
  * ```ts
2414
2414
  * import type { SplitPanes } from 'xlvane';
2415
2415
  *
2416
- * const split: SplitPanes = { x: 120, y: 60 };
2417
- * console.log(split.x);
2416
+ * const split: SplitPanes = { x: 120, y: 60, topLeftCell: 'H20' };
2417
+ * console.log(split.x, split.topLeftCell);
2418
2418
  * ```
2419
2419
  */
2420
2420
  interface SplitPanes {
2421
2421
  readonly x: number;
2422
2422
  readonly y: number;
2423
+ /**
2424
+ * The top-left visible cell of the bottom-right pane (`pane@topLeftCell`): its column is the
2425
+ * first one shown right of the vertical bar, its row the first one shown below the horizontal
2426
+ * bar. Absent when the file does not set it.
2427
+ */
2428
+ readonly topLeftCell?: string;
2423
2429
  }
2424
2430
  /**
2425
2431
  * Display settings of a sheet's view (`sheetView`, ECMA-376 Part 1 §18.3.1.87, W2-51). Frozen
@@ -2580,6 +2586,12 @@ interface Sheet {
2580
2586
  readonly name: string;
2581
2587
  /** 0-based position in the workbook. */
2582
2588
  readonly index: number;
2589
+ /**
2590
+ * The sheet's id in the file (`sheet@sheetId`), which does not depend on its position; `null`
2591
+ * when the file has none, an invalid one or one repeated from an earlier sheet (reported as
2592
+ * `XLV_W_INVALID_VALUE`).
2593
+ */
2594
+ readonly sheetId: number | null;
2583
2595
  readonly kind: SheetKind;
2584
2596
  readonly state: SheetState;
2585
2597
  /** Part name of the sheet, for example `/xl/worksheets/sheet1.xml`; `null` if it is missing. */
@@ -2943,6 +2955,8 @@ interface InspectColumn {
2943
2955
  interface InspectSheet {
2944
2956
  readonly name: string;
2945
2957
  readonly index: number;
2958
+ /** The sheet's id in the file (`Sheet.sheetId`), or `null` when it is missing, invalid or repeated. */
2959
+ readonly sheetId: number | null;
2946
2960
  readonly kind: SheetKind;
2947
2961
  readonly state: SheetState;
2948
2962
  /** Range declared in the file, or `null`. */
@@ -3091,6 +3105,8 @@ declare function inspect(input: Uint8Array | ArrayBuffer | Workbook, options?: I
3091
3105
  interface WriteSheetData {
3092
3106
  readonly name: string;
3093
3107
  readonly state: SheetState;
3108
+ /** The given sheet id, or `null` for the lowest unused one (D-F03). */
3109
+ readonly sheetId?: number | null;
3094
3110
  readonly cells: ReadonlyMap<number, ReadonlyMap<number, StoredCell>>;
3095
3111
  /** Width, visibility and interned style id (0 = none) by column number. */
3096
3112
  readonly columns: ReadonlyMap<number, {
@@ -3228,13 +3244,19 @@ interface CreateWorkbookOptions {
3228
3244
  * ```ts
3229
3245
  * import { createWorkbook, type AddSheetOptions } from 'xlvane';
3230
3246
  *
3231
- * const options: AddSheetOptions = { state: 'hidden' };
3247
+ * const options: AddSheetOptions = { state: 'hidden', sheetId: 3 };
3232
3248
  * createWorkbook().addSheet('Lookup', options);
3233
3249
  * ```
3234
3250
  */
3235
3251
  interface AddSheetOptions {
3236
3252
  /** Visibility. Default `"visible"`. */
3237
3253
  readonly state?: SheetState;
3254
+ /**
3255
+ * The sheet's id in the file (`sheet@sheetId`), a whole number from 1 to 65534, unique in the
3256
+ * workbook. It does not change the sheet's position. Sheets without one get the lowest unused
3257
+ * ids when the workbook is written, so a workbook without given ids has the ids 1, 2, 3, ….
3258
+ */
3259
+ readonly sheetId?: number;
3238
3260
  }
3239
3261
  /**
3240
3262
  * Options for {@link NewSheet.column}.
@@ -3423,8 +3445,10 @@ interface SheetViewOptions {
3423
3445
  readonly layout?: SheetLayout;
3424
3446
  /**
3425
3447
  * Splits the window (without freezing) at `x` points from the left and `y` points from the
3426
- * top; a missing one is 0, but not both. `null` removes the split. A split replaces frozen
3427
- * panes, and {@link NewSheet.freeze} replaces a split.
3448
+ * top; a missing one is 0, but not both. `topLeftCell` (optional) is the top-left visible cell
3449
+ * of the bottom-right pane: the first column shown right of the vertical bar and the first row
3450
+ * below the horizontal bar. `null` removes the split. A split replaces frozen panes, and
3451
+ * {@link NewSheet.freeze} replaces a split.
3428
3452
  */
3429
3453
  readonly split?: Partial<SplitPanes> | null;
3430
3454
  /** The top-left visible cell (`sheetView@topLeftCell`), e.g. `"C10"`; `null` for the default. */
@@ -3506,6 +3530,8 @@ declare class NewSheet {
3506
3530
  private readonly book;
3507
3531
  /** Visibility of the sheet. */
3508
3532
  readonly state: SheetState;
3533
+ /** The id given to {@link NewWorkbook.addSheet}, or `null` when the lowest unused id is taken when writing. */
3534
+ readonly sheetId: number | null;
3509
3535
  /** @internal */
3510
3536
  readonly cellMap: Map<number, Map<number, StoredCell>>;
3511
3537
  /** @internal */
@@ -3564,7 +3590,7 @@ declare class NewSheet {
3564
3590
  readonly tableList: NormalizedTable[];
3565
3591
  private count;
3566
3592
  /** @internal Use {@link NewWorkbook.addSheet}. */
3567
- constructor(name: string, state: SheetState, book: NewWorkbook);
3593
+ constructor(name: string, state: SheetState, book: NewWorkbook, sheetId?: number | null);
3568
3594
  /** Number of cells that have a value or a style. */
3569
3595
  get cellCount(): number;
3570
3596
  private address;
@@ -4006,7 +4032,7 @@ declare class NewWorkbook {
4006
4032
  *
4007
4033
  * const workbook = createWorkbook();
4008
4034
  * workbook.addSheet('Data');
4009
- * workbook.addSheet('Lookup', { state: 'hidden' });
4035
+ * workbook.addSheet('Lookup', { state: 'hidden', sheetId: 5 });
4010
4036
  * ```
4011
4037
  */
4012
4038
  addSheet(name: string, options?: AddSheetOptions): NewSheet;
@@ -4549,6 +4575,8 @@ interface StreamRow {
4549
4575
  interface StreamSheetInfo {
4550
4576
  readonly name: string;
4551
4577
  readonly index: number;
4578
+ /** `sheet@sheetId`, as `Sheet.sheetId`. */
4579
+ readonly sheetId: number | null;
4552
4580
  readonly kind: SheetKind;
4553
4581
  readonly state: SheetState;
4554
4582
  }
@@ -4753,6 +4781,8 @@ type StreamRowInput = readonly CellInput[] | {
4753
4781
  interface StreamSheetSpec {
4754
4782
  readonly name: string;
4755
4783
  readonly state?: SheetState;
4784
+ /** The sheet's id in the file, as for `addSheet` (1 to 65534, unique; D-F03). */
4785
+ readonly sheetId?: number;
4756
4786
  /** Column options by position (column A first); `undefined` entries keep the default. */
4757
4787
  readonly columns?: readonly (ColumnOptions | undefined)[];
4758
4788
  readonly freeze?: FreezeOptions;
@@ -104,6 +104,13 @@
104
104
  "type": "integer",
105
105
  "minimum": 0
106
106
  },
107
+ "sheetId": {
108
+ "type": [
109
+ "integer",
110
+ "null"
111
+ ],
112
+ "minimum": 0
113
+ },
107
114
  "kind": {
108
115
  "enum": [
109
116
  "worksheet",
@@ -454,6 +461,7 @@
454
461
  "required": [
455
462
  "name",
456
463
  "index",
464
+ "sheetId",
457
465
  "kind",
458
466
  "state",
459
467
  "dimension",
package/docs/errors.md CHANGED
@@ -255,7 +255,7 @@ The CSV text given to `fromCSV` has a quoted field that is never closed, text ri
255
255
  `hashPassword` and `verifyPassword` throw it for a hash algorithm other than SHA-1, SHA-256, SHA-384 and SHA-512, and `hashPassword` without a `salt` throws it in a runtime that has no `crypto.getRandomValues` (pass your own random salt there; SR-50, D-C08).
256
256
  `editCells` throws it for an array or shared formula in an edit (it writes plain formulas only), and for a cell inside an array formula's range or the master of a shared formula with other cells in its range (D-E02); `details.cell` names it.
257
257
  `appendRows` throws it when a table ends with a totals row on the sheet's last used row (the new rows would fall below the totals row; the hint gives the `insertRows` call that grows the table instead), or when growing a table for the new rows would overlap another table (D-R03).
258
- `insertRows` and `deleteRows` (and `xlvane edit --insert-rows` / `--delete-rows`) also throw it when the sheet has content whose cell references xlvane cannot move yet: tables, charts, shapes and other drawing objects (pictures alone are moved, and deleting rows that would cut a picture is refused), pivot tables, form controls, embedded objects, custom sheet views, threaded comments, extended content such as data bars or sparklines, a chart or pivot cache elsewhere that names the sheet, a data table, an array formula the change would split, or a formula or defined name that cannot be adjusted safely (for example a 3-D reference such as `Jan:Mar!A1` that includes the sheet, D-B01). The message names the part or the cell, and `details` gives the element, relationship type or formula.
258
+ `insertRows` and `deleteRows` (and `xlvane edit --insert-rows` / `--delete-rows`) also throw it when the sheet has content whose cell references xlvane cannot move yet: tables, charts, shapes and other drawing objects (pictures alone are moved, and deleting rows that would cut a picture is refused), pivot tables, form controls, embedded objects, custom sheet views, threaded comments, extended content such as sparklines or slicers (the conditional formats and data validations of the [MS-XLSX] extension are moved, D-F04), a chart or pivot cache elsewhere that names the sheet, a data table, an array formula the change would split, or a formula or defined name that cannot be adjusted safely (for example a 3-D reference such as `Jan:Mar!A1` that includes the sheet, D-B01). The message names the part or the cell, and `details` gives the element, relationship type or formula.
259
259
  **Fix:** for `@xlvane/compat`, see docs/exceljs-compat.md for the supported subset; remove the call, or keep using exceljs for files that need the feature.
260
260
  For `insertRows`/`deleteRows`, insert or delete the rows in a spreadsheet application, or remove the named content first.
261
261