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.
- package/README.md +15 -9
- package/dist/cli.js +340 -29
- package/dist/index.d.ts +6 -7
- package/dist/index.js +3 -3
- package/dist/{node-files-uazELnqk.js → node-files-B0iqSJIJ.js} +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{options-B7UhudO_.js → options-CwSp2pA4.js} +87 -12
- package/dist/{rows-Cwte1t8m.js → rows-DKwlr0rg.js} +222 -75
- package/dist/{stream-write-CTgWff2P.js → stream-write-BKppyMz0.js} +9 -2
- package/dist/{stream-write-Q-sef804.d.ts → stream-write-GJXORYQ2.d.ts} +37 -7
- package/docs/cli-json/inspect.schema.json +8 -0
- package/docs/errors.md +1 -1
- package/docs/exceljs-compat.md +3 -2
- package/docs/inspect-json.md +2 -0
- package/llms-full.txt +31 -14
- package/package.json +1 -1
- package/skills/xlvane/SKILL.md +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $
|
|
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, `$
|
|
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(
|
|
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 =
|
|
2653
|
-
if (out === null) return
|
|
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
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
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
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
if (
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
if (
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
col
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
if (
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
e.
|
|
2775
|
-
out
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
if (
|
|
2781
|
-
const
|
|
2782
|
-
if (
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
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 (
|
|
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 === "")
|
|
2938
|
-
|
|
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
|
|
3453
|
-
*
|
|
3454
|
-
*
|
|
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 (
|
|
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 { $
|
|
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
|
|
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. `
|
|
3427
|
-
*
|
|
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
|
|
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
|
|