@devmm/puredocs-excel 1.0.4 → 1.0.6
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/LICENSE +21 -0
- package/dist/index.cjs +2020 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +581 -31
- package/dist/index.d.ts +581 -31
- package/dist/index.js +2010 -89
- package/dist/index.js.map +1 -1
- package/package.json +55 -55
package/dist/index.cjs
CHANGED
|
@@ -50,10 +50,10 @@ function parseMinimal(xmlString) {
|
|
|
50
50
|
}
|
|
51
51
|
function parseAttributes(raw) {
|
|
52
52
|
const attrs = {};
|
|
53
|
-
const re = /(
|
|
53
|
+
const re = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
54
54
|
let m;
|
|
55
55
|
while ((m = re.exec(raw)) !== null) {
|
|
56
|
-
attrs[m[1]] = m[2]
|
|
56
|
+
attrs[m[1]] = decodeXmlEntities(m[2] ?? m[3] ?? "");
|
|
57
57
|
}
|
|
58
58
|
return attrs;
|
|
59
59
|
}
|
|
@@ -107,7 +107,7 @@ function parseMinimal(xmlString) {
|
|
|
107
107
|
} else {
|
|
108
108
|
const textStart = pos;
|
|
109
109
|
skipUntil("<");
|
|
110
|
-
const text = xmlString.slice(textStart, pos)
|
|
110
|
+
const text = decodeXmlEntities(xmlString.slice(textStart, pos));
|
|
111
111
|
if (stack.length > 0 && text.trim()) {
|
|
112
112
|
stack[stack.length - 1].textContent += text;
|
|
113
113
|
}
|
|
@@ -116,6 +116,10 @@ function parseMinimal(xmlString) {
|
|
|
116
116
|
if (!root) throw new Error("XML parse error: empty document.");
|
|
117
117
|
return root;
|
|
118
118
|
}
|
|
119
|
+
function decodeXmlEntities(value) {
|
|
120
|
+
if (value.indexOf("&") === -1) return value;
|
|
121
|
+
return value.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
122
|
+
}
|
|
119
123
|
function parseXml(xmlString) {
|
|
120
124
|
if (typeof DOMParser !== "undefined") {
|
|
121
125
|
return parseDom(xmlString);
|
|
@@ -296,7 +300,9 @@ var REL_TYPES = {
|
|
|
296
300
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
297
301
|
chart: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
|
|
298
302
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
299
|
-
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
|
303
|
+
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
304
|
+
comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
|
|
305
|
+
vmlDrawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
|
|
300
306
|
};
|
|
301
307
|
var CONTENT_TYPES = {
|
|
302
308
|
workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
@@ -306,23 +312,32 @@ var CONTENT_TYPES = {
|
|
|
306
312
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
307
313
|
chart: "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
|
|
308
314
|
relationships: "application/vnd.openxmlformats-package.relationships+xml",
|
|
309
|
-
coreProperties: "application/vnd.openxmlformats-package.core-properties+xml"
|
|
315
|
+
coreProperties: "application/vnd.openxmlformats-package.core-properties+xml",
|
|
316
|
+
comments: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
|
|
317
|
+
vmlDrawing: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
310
318
|
};
|
|
311
319
|
|
|
312
320
|
// src/io/ooxml-templates.ts
|
|
313
|
-
function buildContentTypes(sheetCount, extraOverrides = []) {
|
|
321
|
+
function buildContentTypes(sheetCount, extraOverrides = [], extraDefaults = []) {
|
|
314
322
|
const overrides = Array.from(
|
|
315
323
|
{ length: sheetCount },
|
|
316
324
|
(_, i) => ` <Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="${CONTENT_TYPES.worksheet}"/>`
|
|
317
325
|
);
|
|
318
|
-
|
|
326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
327
|
+
for (const { partName, contentType } of [...extraOverrides].reverse()) {
|
|
328
|
+
if (seen.has(partName)) continue;
|
|
329
|
+
seen.add(partName);
|
|
319
330
|
overrides.push(` <Override PartName="${partName}" ContentType="${contentType}"/>`);
|
|
320
331
|
}
|
|
332
|
+
const defaults = extraDefaults.map(
|
|
333
|
+
({ extension, contentType }) => ` <Default Extension="${extension}" ContentType="${contentType}"/>`
|
|
334
|
+
);
|
|
321
335
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
322
336
|
<Types xmlns="${"http://schemas.openxmlformats.org/package/2006/content-types"}">
|
|
323
337
|
<Default Extension="rels" ContentType="${CONTENT_TYPES.relationships}"/>
|
|
324
338
|
<Default Extension="xml" ContentType="application/xml"/>
|
|
325
|
-
|
|
339
|
+
${defaults.length > 0 ? `${defaults.join("\n")}
|
|
340
|
+
` : ""} <Override PartName="/xl/workbook.xml" ContentType="${CONTENT_TYPES.workbook}"/>
|
|
326
341
|
<Override PartName="/xl/styles.xml" ContentType="${CONTENT_TYPES.styles}"/>
|
|
327
342
|
<Override PartName="/xl/sharedStrings.xml" ContentType="${CONTENT_TYPES.sharedStrings}"/>
|
|
328
343
|
${overrides.join("\n")}
|
|
@@ -401,6 +416,95 @@ ${relationships.join("\n")}
|
|
|
401
416
|
</Relationships>`;
|
|
402
417
|
}
|
|
403
418
|
|
|
419
|
+
// src/io/preserved-parts.ts
|
|
420
|
+
function isCoreOwned(path) {
|
|
421
|
+
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/");
|
|
422
|
+
}
|
|
423
|
+
function collectPreservedParts(entries) {
|
|
424
|
+
const preserved = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const [path, bytes] of entries) {
|
|
426
|
+
if (!isCoreOwned(path)) preserved.set(path, bytes);
|
|
427
|
+
}
|
|
428
|
+
return preserved;
|
|
429
|
+
}
|
|
430
|
+
function parseContentTypes(xml2) {
|
|
431
|
+
const defaults = /* @__PURE__ */ new Map();
|
|
432
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
433
|
+
for (const m of xml2.matchAll(/<Default\s+[^>]*\/?>/g)) {
|
|
434
|
+
const ext = /Extension="([^"]*)"/.exec(m[0])?.[1];
|
|
435
|
+
const ct = /ContentType="([^"]*)"/.exec(m[0])?.[1];
|
|
436
|
+
if (ext && ct) defaults.set(ext.toLowerCase(), ct);
|
|
437
|
+
}
|
|
438
|
+
for (const m of xml2.matchAll(/<Override\s+[^>]*\/?>/g)) {
|
|
439
|
+
const part = /PartName="([^"]*)"/.exec(m[0])?.[1];
|
|
440
|
+
const ct = /ContentType="([^"]*)"/.exec(m[0])?.[1];
|
|
441
|
+
if (part && ct) overrides.set(part, ct);
|
|
442
|
+
}
|
|
443
|
+
return { defaults, overrides };
|
|
444
|
+
}
|
|
445
|
+
function resolveTarget(target, baseDir) {
|
|
446
|
+
if (target.startsWith("/")) return target.slice(1);
|
|
447
|
+
const segments = baseDir.split("/").filter(Boolean);
|
|
448
|
+
for (const seg of target.split("/")) {
|
|
449
|
+
if (seg === "." || seg === "") continue;
|
|
450
|
+
if (seg === "..") segments.pop();
|
|
451
|
+
else segments.push(seg);
|
|
452
|
+
}
|
|
453
|
+
return segments.join("/");
|
|
454
|
+
}
|
|
455
|
+
function parseRelationships(relsXml, ownerPath) {
|
|
456
|
+
const baseDir = ownerPath.slice(0, ownerPath.lastIndexOf("/"));
|
|
457
|
+
const rels = [];
|
|
458
|
+
for (const m of relsXml.matchAll(/<Relationship\s+[^>]*\/?>/g)) {
|
|
459
|
+
const tag = m[0];
|
|
460
|
+
const id = /\bId="([^"]*)"/.exec(tag)?.[1];
|
|
461
|
+
const type = /\bType="([^"]*)"/.exec(tag)?.[1];
|
|
462
|
+
const rawTarget = /\bTarget="([^"]*)"/.exec(tag)?.[1];
|
|
463
|
+
if (!id || !type || rawTarget === void 0) continue;
|
|
464
|
+
const target = decodeXmlEntities(rawTarget);
|
|
465
|
+
const external = /TargetMode="External"/.test(tag);
|
|
466
|
+
rels.push({
|
|
467
|
+
id,
|
|
468
|
+
type,
|
|
469
|
+
target,
|
|
470
|
+
resolved: external ? void 0 : resolveTarget(target, baseDir),
|
|
471
|
+
external
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return rels;
|
|
475
|
+
}
|
|
476
|
+
function collectSubtree(parts, startPath) {
|
|
477
|
+
const seen = /* @__PURE__ */ new Set();
|
|
478
|
+
const queue = [startPath];
|
|
479
|
+
while (queue.length > 0) {
|
|
480
|
+
const path = queue.pop();
|
|
481
|
+
if (seen.has(path) || !parts.has(path)) continue;
|
|
482
|
+
seen.add(path);
|
|
483
|
+
const dir = path.slice(0, path.lastIndexOf("/"));
|
|
484
|
+
const file = path.slice(path.lastIndexOf("/") + 1);
|
|
485
|
+
const relsPath = `${dir}/_rels/${file}.rels`;
|
|
486
|
+
const relsBytes = parts.get(relsPath);
|
|
487
|
+
if (!relsBytes) continue;
|
|
488
|
+
seen.add(relsPath);
|
|
489
|
+
for (const rel of parseRelationships(decodeUtf8(relsBytes), path)) {
|
|
490
|
+
if (rel.resolved) queue.push(rel.resolved);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return seen;
|
|
494
|
+
}
|
|
495
|
+
var PRESERVED_TAIL_TAGS = ["drawing", "legacyDrawing", "legacyDrawingHF", "picture"];
|
|
496
|
+
function parseTailElements(worksheetXml) {
|
|
497
|
+
const found = [];
|
|
498
|
+
for (const tag of PRESERVED_TAIL_TAGS) {
|
|
499
|
+
const re = new RegExp(`<(?:\\w+:)?${tag}\\s[^>]*?/>|<(?:\\w+:)?${tag}\\s[^>]*?>\\s*</(?:\\w+:)?${tag}>`, "g");
|
|
500
|
+
for (const m of worksheetXml.matchAll(re)) {
|
|
501
|
+
const relId = /r:id="([^"]*)"/.exec(m[0])?.[1];
|
|
502
|
+
if (relId) found.push({ tag, relId, xml: m[0] });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return found;
|
|
506
|
+
}
|
|
507
|
+
|
|
404
508
|
// src/model/shared-string-manager.ts
|
|
405
509
|
var _stringToIndex, _indexToString;
|
|
406
510
|
var SharedStringManager = class {
|
|
@@ -1550,12 +1654,305 @@ function escAttr(s) {
|
|
|
1550
1654
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1551
1655
|
}
|
|
1552
1656
|
|
|
1657
|
+
// src/style/number-format-renderer.ts
|
|
1658
|
+
function splitSections(code) {
|
|
1659
|
+
const sections = [];
|
|
1660
|
+
let current = "";
|
|
1661
|
+
let inQuote = false;
|
|
1662
|
+
for (let i = 0; i < code.length; i++) {
|
|
1663
|
+
const ch = code[i];
|
|
1664
|
+
if (ch === '"') {
|
|
1665
|
+
inQuote = !inQuote;
|
|
1666
|
+
current += ch;
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
if (ch === "\\" && i + 1 < code.length) {
|
|
1670
|
+
current += ch + code[++i];
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
if (ch === ";" && !inQuote) {
|
|
1674
|
+
sections.push(current);
|
|
1675
|
+
current = "";
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
current += ch;
|
|
1679
|
+
}
|
|
1680
|
+
sections.push(current);
|
|
1681
|
+
return sections;
|
|
1682
|
+
}
|
|
1683
|
+
function hasDateTokens(section) {
|
|
1684
|
+
const bare = section.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "");
|
|
1685
|
+
return /[ymdhsYMDHS]/.test(bare);
|
|
1686
|
+
}
|
|
1687
|
+
function isUnsupported(section) {
|
|
1688
|
+
if (/\[[<>=]/.test(section)) return true;
|
|
1689
|
+
if (/[?]/.test(section)) return true;
|
|
1690
|
+
if (/[Ee][+-]/.test(section)) return true;
|
|
1691
|
+
if (hasDateTokens(section)) return true;
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
function parseSection(section) {
|
|
1695
|
+
const stripped = section.replace(
|
|
1696
|
+
/\[(\$([^\]-]*)[^\]]*|[^\]]*)\]/g,
|
|
1697
|
+
(_m, _all, currency) => typeof currency === "string" ? currency : ""
|
|
1698
|
+
);
|
|
1699
|
+
let prefix = "";
|
|
1700
|
+
let suffix = "";
|
|
1701
|
+
let pattern = "";
|
|
1702
|
+
let seenPattern = false;
|
|
1703
|
+
let percentScale = 1;
|
|
1704
|
+
for (let i = 0; i < stripped.length; i++) {
|
|
1705
|
+
const ch = stripped[i];
|
|
1706
|
+
if (ch === '"') {
|
|
1707
|
+
const end = stripped.indexOf('"', i + 1);
|
|
1708
|
+
const literal = stripped.slice(i + 1, end < 0 ? void 0 : end);
|
|
1709
|
+
if (seenPattern) suffix += literal;
|
|
1710
|
+
else prefix += literal;
|
|
1711
|
+
i = end < 0 ? stripped.length : end;
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
if (ch === "\\" && i + 1 < stripped.length) {
|
|
1715
|
+
const literal = stripped[++i];
|
|
1716
|
+
if (seenPattern) suffix += literal;
|
|
1717
|
+
else prefix += literal;
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
if (ch === "_" || ch === "*") {
|
|
1721
|
+
i++;
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
if (ch === "#" || ch === "0" || ch === "." || ch === ",") {
|
|
1725
|
+
pattern += ch;
|
|
1726
|
+
seenPattern = true;
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
if (ch === "%") {
|
|
1730
|
+
percentScale *= 100;
|
|
1731
|
+
if (seenPattern) suffix += ch;
|
|
1732
|
+
else prefix += ch;
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
if (seenPattern) suffix += ch;
|
|
1736
|
+
else prefix += ch;
|
|
1737
|
+
}
|
|
1738
|
+
const dot = pattern.indexOf(".");
|
|
1739
|
+
const intPart = dot < 0 ? pattern : pattern.slice(0, dot);
|
|
1740
|
+
const fracPart = dot < 0 ? "" : pattern.slice(dot + 1);
|
|
1741
|
+
return {
|
|
1742
|
+
literalOnly: !seenPattern,
|
|
1743
|
+
prefix,
|
|
1744
|
+
suffix,
|
|
1745
|
+
minIntDigits: (intPart.match(/0/g) ?? []).length,
|
|
1746
|
+
decimals: (fracPart.match(/[#0]/g) ?? []).length,
|
|
1747
|
+
useGrouping: intPart.includes(","),
|
|
1748
|
+
percentScale
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
function renderNumber(value, p) {
|
|
1752
|
+
if (p.literalOnly) return p.prefix + p.suffix;
|
|
1753
|
+
const scaled = value * p.percentScale;
|
|
1754
|
+
const fixed = scaled.toFixed(p.decimals);
|
|
1755
|
+
let [intDigits = "0", fracDigits = ""] = fixed.split(".");
|
|
1756
|
+
if (intDigits.length < p.minIntDigits) {
|
|
1757
|
+
intDigits = intDigits.padStart(p.minIntDigits, "0");
|
|
1758
|
+
}
|
|
1759
|
+
if (p.useGrouping) {
|
|
1760
|
+
intDigits = intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1761
|
+
}
|
|
1762
|
+
return p.prefix + intDigits + (fracDigits ? `.${fracDigits}` : "") + p.suffix;
|
|
1763
|
+
}
|
|
1764
|
+
function formatNumberWithCode(value, formatCode) {
|
|
1765
|
+
if (!Number.isFinite(value)) return null;
|
|
1766
|
+
if (!formatCode || formatCode === "General") return null;
|
|
1767
|
+
if (formatCode === "@") return String(value);
|
|
1768
|
+
const sections = splitSections(formatCode);
|
|
1769
|
+
const negative = sections[1];
|
|
1770
|
+
const zero = sections[2];
|
|
1771
|
+
let section;
|
|
1772
|
+
let magnitude = value;
|
|
1773
|
+
if (value === 0 && zero !== void 0) {
|
|
1774
|
+
section = zero;
|
|
1775
|
+
} else if (value < 0 && negative !== void 0) {
|
|
1776
|
+
section = negative;
|
|
1777
|
+
magnitude = Math.abs(value);
|
|
1778
|
+
} else {
|
|
1779
|
+
section = sections[0];
|
|
1780
|
+
}
|
|
1781
|
+
if (section === void 0 || isUnsupported(section)) return null;
|
|
1782
|
+
return renderNumber(magnitude, parseSection(section));
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/style/date-format-renderer.ts
|
|
1786
|
+
var MONTHS = [
|
|
1787
|
+
"January",
|
|
1788
|
+
"February",
|
|
1789
|
+
"March",
|
|
1790
|
+
"April",
|
|
1791
|
+
"May",
|
|
1792
|
+
"June",
|
|
1793
|
+
"July",
|
|
1794
|
+
"August",
|
|
1795
|
+
"September",
|
|
1796
|
+
"October",
|
|
1797
|
+
"November",
|
|
1798
|
+
"December"
|
|
1799
|
+
];
|
|
1800
|
+
var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1801
|
+
function firstSection(code) {
|
|
1802
|
+
let inQuote = false;
|
|
1803
|
+
for (let i = 0; i < code.length; i++) {
|
|
1804
|
+
const ch = code[i];
|
|
1805
|
+
if (ch === '"') inQuote = !inQuote;
|
|
1806
|
+
else if (ch === ";" && !inQuote) return code.slice(0, i);
|
|
1807
|
+
}
|
|
1808
|
+
return code;
|
|
1809
|
+
}
|
|
1810
|
+
function tokenize(section) {
|
|
1811
|
+
const tokens = [];
|
|
1812
|
+
let i = 0;
|
|
1813
|
+
const n = section.length;
|
|
1814
|
+
while (i < n) {
|
|
1815
|
+
const ch = section[i];
|
|
1816
|
+
if (ch === '"') {
|
|
1817
|
+
let j = i + 1;
|
|
1818
|
+
let text = "";
|
|
1819
|
+
while (j < n && section[j] !== '"') text += section[j++];
|
|
1820
|
+
tokens.push({ kind: "literal", text });
|
|
1821
|
+
i = j + 1;
|
|
1822
|
+
continue;
|
|
1823
|
+
}
|
|
1824
|
+
if (ch === "\\") {
|
|
1825
|
+
tokens.push({ kind: "literal", text: section[i + 1] ?? "" });
|
|
1826
|
+
i += 2;
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
if (ch === "[") {
|
|
1830
|
+
const close = section.indexOf("]", i);
|
|
1831
|
+
if (close === -1) return null;
|
|
1832
|
+
const inner = section.slice(i + 1, close);
|
|
1833
|
+
if (/^[hmsHMS]+$/.test(inner)) return null;
|
|
1834
|
+
i = close + 1;
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
const rest = section.slice(i);
|
|
1838
|
+
const ampm = /^(AM\/PM|A\/P|am\/pm|a\/p)/.exec(rest);
|
|
1839
|
+
if (ampm) {
|
|
1840
|
+
tokens.push({ kind: "ampm", text: ampm[1] });
|
|
1841
|
+
i += ampm[1].length;
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
const lower = ch.toLowerCase();
|
|
1845
|
+
if ("ymdhs".includes(lower)) {
|
|
1846
|
+
let j = i;
|
|
1847
|
+
while (j < n && section[j].toLowerCase() === lower) j++;
|
|
1848
|
+
tokens.push({ kind: lower, text: section.slice(i, j) });
|
|
1849
|
+
i = j;
|
|
1850
|
+
if (lower === "s" && section[i] === "." && /\d|0/.test(section[i + 1] ?? "")) return null;
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
if (/[a-zA-Z]/.test(ch)) return null;
|
|
1854
|
+
tokens.push({ kind: "literal", text: ch });
|
|
1855
|
+
i++;
|
|
1856
|
+
}
|
|
1857
|
+
return tokens;
|
|
1858
|
+
}
|
|
1859
|
+
function resolveMonthVsMinute(tokens) {
|
|
1860
|
+
const dateKinds = /* @__PURE__ */ new Set(["y", "m", "d", "h", "s"]);
|
|
1861
|
+
for (let t = 0; t < tokens.length; t++) {
|
|
1862
|
+
const token = tokens[t];
|
|
1863
|
+
if (token.kind !== "m" || token.text.length > 2) continue;
|
|
1864
|
+
let prev;
|
|
1865
|
+
for (let p = t - 1; p >= 0; p--) {
|
|
1866
|
+
if (dateKinds.has(tokens[p].kind)) {
|
|
1867
|
+
prev = tokens[p];
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
let next;
|
|
1872
|
+
for (let q = t + 1; q < tokens.length; q++) {
|
|
1873
|
+
if (dateKinds.has(tokens[q].kind)) {
|
|
1874
|
+
next = tokens[q];
|
|
1875
|
+
break;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (prev?.kind === "h" || next?.kind === "s") token.kind = "minute";
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
var pad = (v, len) => String(v).padStart(len, "0");
|
|
1882
|
+
function formatDateCode(date, formatCode) {
|
|
1883
|
+
if (!formatCode || formatCode === "General") return null;
|
|
1884
|
+
const tokens = tokenize(firstSection(formatCode));
|
|
1885
|
+
if (!tokens) return null;
|
|
1886
|
+
if (!tokens.some((t) => "ymdhs".includes(t.kind))) return null;
|
|
1887
|
+
resolveMonthVsMinute(tokens);
|
|
1888
|
+
const twelveHour = tokens.some((t) => t.kind === "ampm");
|
|
1889
|
+
const hours24 = date.getHours();
|
|
1890
|
+
const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
|
|
1891
|
+
let out = "";
|
|
1892
|
+
for (const t of tokens) {
|
|
1893
|
+
switch (t.kind) {
|
|
1894
|
+
case "literal":
|
|
1895
|
+
out += t.text;
|
|
1896
|
+
break;
|
|
1897
|
+
case "y":
|
|
1898
|
+
out += t.text.length <= 2 ? pad(date.getFullYear() % 100, 2) : String(date.getFullYear());
|
|
1899
|
+
break;
|
|
1900
|
+
case "m": {
|
|
1901
|
+
const month = date.getMonth();
|
|
1902
|
+
if (t.text.length === 1) out += String(month + 1);
|
|
1903
|
+
else if (t.text.length === 2) out += pad(month + 1, 2);
|
|
1904
|
+
else if (t.text.length === 3) out += MONTHS[month].slice(0, 3);
|
|
1905
|
+
else if (t.text.length === 5) out += MONTHS[month][0];
|
|
1906
|
+
else out += MONTHS[month];
|
|
1907
|
+
break;
|
|
1908
|
+
}
|
|
1909
|
+
case "minute":
|
|
1910
|
+
out += t.text.length === 1 ? String(date.getMinutes()) : pad(date.getMinutes(), 2);
|
|
1911
|
+
break;
|
|
1912
|
+
case "d": {
|
|
1913
|
+
const day = date.getDate();
|
|
1914
|
+
if (t.text.length === 1) out += String(day);
|
|
1915
|
+
else if (t.text.length === 2) out += pad(day, 2);
|
|
1916
|
+
else if (t.text.length === 3) out += DAYS[date.getDay()].slice(0, 3);
|
|
1917
|
+
else out += DAYS[date.getDay()];
|
|
1918
|
+
break;
|
|
1919
|
+
}
|
|
1920
|
+
case "h": {
|
|
1921
|
+
const h = twelveHour ? hours12 : hours24;
|
|
1922
|
+
out += t.text.length === 1 ? String(h) : pad(h, 2);
|
|
1923
|
+
break;
|
|
1924
|
+
}
|
|
1925
|
+
case "s":
|
|
1926
|
+
out += t.text.length === 1 ? String(date.getSeconds()) : pad(date.getSeconds(), 2);
|
|
1927
|
+
break;
|
|
1928
|
+
case "ampm": {
|
|
1929
|
+
const isAm = hours24 < 12;
|
|
1930
|
+
if (t.text === "a/p") out += isAm ? "a" : "p";
|
|
1931
|
+
else if (t.text === "A/P") out += isAm ? "A" : "P";
|
|
1932
|
+
else if (t.text === "am/pm") out += isAm ? "am" : "pm";
|
|
1933
|
+
else out += isAm ? "AM" : "PM";
|
|
1934
|
+
break;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return out;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1553
1941
|
// src/model/cell-reference.ts
|
|
1942
|
+
var EXCEL_MAX_ROWS = 1048576;
|
|
1943
|
+
var EXCEL_MAX_COLUMNS = 16384;
|
|
1944
|
+
function needsNormalizing(ref) {
|
|
1945
|
+
for (let i = 0; i < ref.length; i++) {
|
|
1946
|
+
const code = ref.charCodeAt(i);
|
|
1947
|
+
if (code === 36 || code >= 97 && code <= 122) return true;
|
|
1948
|
+
}
|
|
1949
|
+
return false;
|
|
1950
|
+
}
|
|
1554
1951
|
function parseCellRef(cellReference) {
|
|
1555
1952
|
if (!cellReference) {
|
|
1556
1953
|
throw new Error("Cell reference cannot be null or empty.");
|
|
1557
1954
|
}
|
|
1558
|
-
const ref = cellReference.replace(/\$/g, "").toUpperCase();
|
|
1955
|
+
const ref = needsNormalizing(cellReference) ? cellReference.replace(/\$/g, "").toUpperCase() : cellReference;
|
|
1559
1956
|
let i = 0;
|
|
1560
1957
|
let column = 0;
|
|
1561
1958
|
while (i < ref.length && ref.charCodeAt(i) >= 65 && ref.charCodeAt(i) <= 90) {
|
|
@@ -1601,6 +1998,11 @@ function columnNumber(letters) {
|
|
|
1601
1998
|
}
|
|
1602
1999
|
return col;
|
|
1603
2000
|
}
|
|
2001
|
+
function formatRangeRef(startRow, startColumn, endRow, endColumn) {
|
|
2002
|
+
const start = cellRefFromRowCol(startRow, startColumn);
|
|
2003
|
+
if (startRow === endRow && startColumn === endColumn) return start;
|
|
2004
|
+
return `${start}:${cellRefFromRowCol(endRow, endColumn)}`;
|
|
2005
|
+
}
|
|
1604
2006
|
function parseRangeRef(rangeReference) {
|
|
1605
2007
|
const parts = rangeReference.split(":");
|
|
1606
2008
|
if (parts.length !== 2) {
|
|
@@ -1749,6 +2151,9 @@ var Cell = class {
|
|
|
1749
2151
|
}
|
|
1750
2152
|
/**
|
|
1751
2153
|
* Returns the display text of the cell value (always a string).
|
|
2154
|
+
*
|
|
2155
|
+
* Numbers are returned unformatted; use {@link getFormattedText} to apply the
|
|
2156
|
+
* cell's number format.
|
|
1752
2157
|
*/
|
|
1753
2158
|
getText() {
|
|
1754
2159
|
const val = this.getValue();
|
|
@@ -1756,6 +2161,23 @@ var Cell = class {
|
|
|
1756
2161
|
if (val instanceof Date) return val.toLocaleDateString();
|
|
1757
2162
|
return String(val);
|
|
1758
2163
|
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Returns the display text with the cell's number format applied, e.g. a
|
|
2166
|
+
* value of 4.5 under `$#,##0.00` renders as `$4.50`, and a date under
|
|
2167
|
+
* `dd/mm/yyyy` as `31/01/2025`.
|
|
2168
|
+
*
|
|
2169
|
+
* Falls back to {@link getText} for values with no format and for format
|
|
2170
|
+
* codes outside the supported subset (fractions, scientific notation,
|
|
2171
|
+
* conditional sections, elapsed time) — so the result is never a misleading
|
|
2172
|
+
* rendering.
|
|
2173
|
+
*/
|
|
2174
|
+
getFormattedText() {
|
|
2175
|
+
const val = this.getValue();
|
|
2176
|
+
if (typeof val !== "number" && !(val instanceof Date)) return this.getText();
|
|
2177
|
+
const format = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).numberFormat;
|
|
2178
|
+
if (!format) return this.getText();
|
|
2179
|
+
return val instanceof Date ? formatDateCode(val, format.formatCode) ?? this.getText() : formatNumberWithCode(val, format.formatCode) ?? this.getText();
|
|
2180
|
+
}
|
|
1759
2181
|
// ── Formula API ────────────────────────────────────────────────────────────
|
|
1760
2182
|
/**
|
|
1761
2183
|
* Sets a formula. The leading '=' is optional.
|
|
@@ -1939,8 +2361,269 @@ function escXml(s) {
|
|
|
1939
2361
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1940
2362
|
}
|
|
1941
2363
|
|
|
2364
|
+
// src/model/ref-shift.ts
|
|
2365
|
+
function shiftIndex(index, shift) {
|
|
2366
|
+
if (shift.kind === "insert") {
|
|
2367
|
+
return index >= shift.at ? index + shift.count : index;
|
|
2368
|
+
}
|
|
2369
|
+
const bandEnd = shift.at + shift.count - 1;
|
|
2370
|
+
if (index < shift.at) return index;
|
|
2371
|
+
if (index <= bandEnd) return null;
|
|
2372
|
+
return index - shift.count;
|
|
2373
|
+
}
|
|
2374
|
+
function shiftSpan(start, end, shift) {
|
|
2375
|
+
if (shift.kind === "insert") {
|
|
2376
|
+
return {
|
|
2377
|
+
start: start >= shift.at ? start + shift.count : start,
|
|
2378
|
+
end: end >= shift.at ? end + shift.count : end
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
const bandEnd = shift.at + shift.count - 1;
|
|
2382
|
+
const newStart = start < shift.at ? start : start <= bandEnd ? shift.at : start - shift.count;
|
|
2383
|
+
const newEnd = end < shift.at ? end : end <= bandEnd ? shift.at - 1 : end - shift.count;
|
|
2384
|
+
return newEnd < newStart ? null : { start: newStart, end: newEnd };
|
|
2385
|
+
}
|
|
2386
|
+
function shiftRangeRef(ref, shift) {
|
|
2387
|
+
const [startRef, endRef] = ref.split(":");
|
|
2388
|
+
const start = parseCellRef(startRef);
|
|
2389
|
+
const end = endRef !== void 0 ? parseCellRef(endRef) : start;
|
|
2390
|
+
const rows = shift.dim === "row" ? shiftSpan(start.row, end.row, shift) : { start: start.row, end: end.row };
|
|
2391
|
+
const cols = shift.dim === "col" ? shiftSpan(start.column, end.column, shift) : { start: start.column, end: end.column };
|
|
2392
|
+
if (!rows || !cols) return null;
|
|
2393
|
+
const startOut = cellRefFromRowCol(rows.start, cols.start);
|
|
2394
|
+
if (endRef === void 0) return startOut;
|
|
2395
|
+
return `${startOut}:${cellRefFromRowCol(rows.end, cols.end)}`;
|
|
2396
|
+
}
|
|
2397
|
+
function shiftSqref(sqref, shift) {
|
|
2398
|
+
const surviving = sqref.split(/\s+/).filter(Boolean).map((part) => shiftRangeRef(part, shift)).filter((part) => part !== null);
|
|
2399
|
+
return surviving.length > 0 ? surviving.join(" ") : null;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// src/model/formula-ref-shift.ts
|
|
2403
|
+
var MAX_ROW = EXCEL_MAX_ROWS;
|
|
2404
|
+
var MAX_COL = EXCEL_MAX_COLUMNS;
|
|
2405
|
+
function isWordChar(code) {
|
|
2406
|
+
return code >= 65 && code <= 90 || // A-Z
|
|
2407
|
+
code >= 97 && code <= 122 || // a-z
|
|
2408
|
+
code >= 48 && code <= 57 || // 0-9
|
|
2409
|
+
code === 95 || // _
|
|
2410
|
+
code === 46 || // .
|
|
2411
|
+
code === 36;
|
|
2412
|
+
}
|
|
2413
|
+
function parseCellComponent(word) {
|
|
2414
|
+
const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
|
|
2415
|
+
if (!m) return null;
|
|
2416
|
+
const col = columnNumber(m[2]);
|
|
2417
|
+
const row = parseInt(m[4], 10);
|
|
2418
|
+
if (col > MAX_COL || row < 1 || row > MAX_ROW) return null;
|
|
2419
|
+
return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
|
|
2420
|
+
}
|
|
2421
|
+
function parseColComponent(word) {
|
|
2422
|
+
const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
|
|
2423
|
+
if (!m) return null;
|
|
2424
|
+
const col = columnNumber(m[2]);
|
|
2425
|
+
if (col > MAX_COL) return null;
|
|
2426
|
+
return { abs: m[1] === "$", col };
|
|
2427
|
+
}
|
|
2428
|
+
function parseRowComponent(word) {
|
|
2429
|
+
const m = /^(\$?)(\d+)$/.exec(word);
|
|
2430
|
+
if (!m) return null;
|
|
2431
|
+
const row = parseInt(m[2], 10);
|
|
2432
|
+
if (row < 1 || row > MAX_ROW) return null;
|
|
2433
|
+
return { abs: m[1] === "$", row };
|
|
2434
|
+
}
|
|
2435
|
+
function emitCell(c, row, col) {
|
|
2436
|
+
return `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
|
|
2437
|
+
}
|
|
2438
|
+
function shiftFormulaRefs(formula, shift, formulaSheet) {
|
|
2439
|
+
const n = formula.length;
|
|
2440
|
+
const editedSheet = shift.sheetName.toUpperCase();
|
|
2441
|
+
const unqualifiedRewritable = formulaSheet.toUpperCase() === editedSheet;
|
|
2442
|
+
const isRowShift = shift.dim === "row";
|
|
2443
|
+
let out = "";
|
|
2444
|
+
let flushed = 0;
|
|
2445
|
+
let changed = false;
|
|
2446
|
+
let hasRefError = false;
|
|
2447
|
+
let i = 0;
|
|
2448
|
+
const replace = (start, text) => {
|
|
2449
|
+
if (text.length === i - start && formula.startsWith(text, start)) return;
|
|
2450
|
+
out += formula.slice(flushed, start) + text;
|
|
2451
|
+
flushed = i;
|
|
2452
|
+
changed = true;
|
|
2453
|
+
};
|
|
2454
|
+
const readWord = () => {
|
|
2455
|
+
let j = i;
|
|
2456
|
+
while (j < n && isWordChar(formula.charCodeAt(j))) j++;
|
|
2457
|
+
const w = formula.slice(i, j);
|
|
2458
|
+
i = j;
|
|
2459
|
+
return w;
|
|
2460
|
+
};
|
|
2461
|
+
const readQuoted = () => {
|
|
2462
|
+
let j = i + 1;
|
|
2463
|
+
while (j < n) {
|
|
2464
|
+
if (formula[j] === "'") {
|
|
2465
|
+
if (formula[j + 1] === "'") {
|
|
2466
|
+
j += 2;
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2469
|
+
j++;
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2472
|
+
j++;
|
|
2473
|
+
}
|
|
2474
|
+
const q = formula.slice(i, j);
|
|
2475
|
+
i = j;
|
|
2476
|
+
return q;
|
|
2477
|
+
};
|
|
2478
|
+
const consumeRef = (refStart, rewritable) => {
|
|
2479
|
+
const first = readWord();
|
|
2480
|
+
let firstCell = parseCellComponent(first);
|
|
2481
|
+
let firstCol = firstCell ? null : parseColComponent(first);
|
|
2482
|
+
let firstRow = firstCell || firstCol ? null : parseRowComponent(first);
|
|
2483
|
+
let second = null;
|
|
2484
|
+
let secondCell = null;
|
|
2485
|
+
let secondCol = null;
|
|
2486
|
+
let secondRow = null;
|
|
2487
|
+
if (formula[i] === ":" && i + 1 < n && isWordChar(formula.charCodeAt(i + 1))) {
|
|
2488
|
+
const save = i;
|
|
2489
|
+
i++;
|
|
2490
|
+
const w = readWord();
|
|
2491
|
+
if (firstCell) secondCell = parseCellComponent(w);
|
|
2492
|
+
else if (firstCol) secondCol = parseColComponent(w);
|
|
2493
|
+
else if (firstRow) secondRow = parseRowComponent(w);
|
|
2494
|
+
if (secondCell || secondCol || secondRow) {
|
|
2495
|
+
second = w;
|
|
2496
|
+
} else {
|
|
2497
|
+
i = save;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
if (second === null && formula[i] === "(") return;
|
|
2501
|
+
const dead = () => {
|
|
2502
|
+
if (formula[i] === "#") i++;
|
|
2503
|
+
replace(refStart, "#REF!");
|
|
2504
|
+
hasRefError = true;
|
|
2505
|
+
};
|
|
2506
|
+
if (second !== null) {
|
|
2507
|
+
if (!rewritable) return;
|
|
2508
|
+
if (firstCell && secondCell) {
|
|
2509
|
+
const a = firstCell, b = secondCell;
|
|
2510
|
+
const rows = isRowShift ? shiftSpan(a.row, b.row, shift) : { start: a.row, end: b.row };
|
|
2511
|
+
const cols = isRowShift ? { start: a.col, end: b.col } : shiftSpan(a.col, b.col, shift);
|
|
2512
|
+
if (!rows || !cols) {
|
|
2513
|
+
dead();
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
replace(refStart, `${emitCell(a, rows.start, cols.start)}:${emitCell(b, rows.end, cols.end)}`);
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
if (firstCol && secondCol) {
|
|
2520
|
+
if (isRowShift) return;
|
|
2521
|
+
const span = shiftSpan(firstCol.col, secondCol.col, shift);
|
|
2522
|
+
if (!span) {
|
|
2523
|
+
dead();
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const a = firstCol.abs ? "$" : "";
|
|
2527
|
+
const b = secondCol.abs ? "$" : "";
|
|
2528
|
+
replace(refStart, `${a}${columnLetter(span.start)}:${b}${columnLetter(span.end)}`);
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
if (firstRow && secondRow) {
|
|
2532
|
+
if (!isRowShift) return;
|
|
2533
|
+
const span = shiftSpan(firstRow.row, secondRow.row, shift);
|
|
2534
|
+
if (!span) {
|
|
2535
|
+
dead();
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const a = firstRow.abs ? "$" : "";
|
|
2539
|
+
const b = secondRow.abs ? "$" : "";
|
|
2540
|
+
replace(refStart, `${a}${span.start}:${b}${span.end}`);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
if (!firstCell || !rewritable) return;
|
|
2546
|
+
const row = isRowShift ? shiftIndex(firstCell.row, shift) : firstCell.row;
|
|
2547
|
+
const col = isRowShift ? firstCell.col : shiftIndex(firstCell.col, shift);
|
|
2548
|
+
if (row === null || col === null) {
|
|
2549
|
+
dead();
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
replace(refStart, emitCell(firstCell, row, col));
|
|
2553
|
+
};
|
|
2554
|
+
while (i < n) {
|
|
2555
|
+
const ch = formula[i];
|
|
2556
|
+
if (ch === '"') {
|
|
2557
|
+
let j = i + 1;
|
|
2558
|
+
while (j < n) {
|
|
2559
|
+
if (formula[j] === '"') {
|
|
2560
|
+
if (formula[j + 1] === '"') {
|
|
2561
|
+
j += 2;
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
j++;
|
|
2565
|
+
break;
|
|
2566
|
+
}
|
|
2567
|
+
j++;
|
|
2568
|
+
}
|
|
2569
|
+
i = j;
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
if (ch === "'") {
|
|
2573
|
+
const quoted = readQuoted();
|
|
2574
|
+
if (formula[i] === "!") {
|
|
2575
|
+
i++;
|
|
2576
|
+
const name = quoted.slice(1, -1);
|
|
2577
|
+
const rewritable = name.indexOf(":") === -1 && (name.indexOf("''") === -1 ? name : name.replace(/''/g, "'")).toUpperCase() === editedSheet;
|
|
2578
|
+
consumeRef(i, rewritable);
|
|
2579
|
+
}
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
if (isWordChar(formula.charCodeAt(i))) {
|
|
2583
|
+
const start = i;
|
|
2584
|
+
const word = readWord();
|
|
2585
|
+
if (formula[i] === "!") {
|
|
2586
|
+
i++;
|
|
2587
|
+
consumeRef(i, word.toUpperCase() === editedSheet);
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
if (formula[i] === ":") {
|
|
2591
|
+
const save = i;
|
|
2592
|
+
i++;
|
|
2593
|
+
const w2 = i < n && isWordChar(formula.charCodeAt(i)) ? readWord() : "";
|
|
2594
|
+
if (w2 && formula[i] === "!") {
|
|
2595
|
+
i++;
|
|
2596
|
+
consumeRef(i, false);
|
|
2597
|
+
continue;
|
|
2598
|
+
}
|
|
2599
|
+
i = save;
|
|
2600
|
+
}
|
|
2601
|
+
if (formula[i] === "[") {
|
|
2602
|
+
let depth = 0;
|
|
2603
|
+
let j = i;
|
|
2604
|
+
while (j < n) {
|
|
2605
|
+
if (formula[j] === "[") depth++;
|
|
2606
|
+
else if (formula[j] === "]" && --depth === 0) {
|
|
2607
|
+
j++;
|
|
2608
|
+
break;
|
|
2609
|
+
}
|
|
2610
|
+
j++;
|
|
2611
|
+
}
|
|
2612
|
+
i = j;
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
i = start;
|
|
2616
|
+
consumeRef(start, unqualifiedRewritable);
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
i++;
|
|
2620
|
+
}
|
|
2621
|
+
if (!changed) return { text: formula, changed: false, hasRefError };
|
|
2622
|
+
return { text: out + formula.slice(flushed), changed: true, hasRefError };
|
|
2623
|
+
}
|
|
2624
|
+
|
|
1942
2625
|
// src/model/range.ts
|
|
1943
|
-
var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn;
|
|
2626
|
+
var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn, forEachExisting_fn;
|
|
1944
2627
|
var Range = class {
|
|
1945
2628
|
constructor(sheet, rangeReference) {
|
|
1946
2629
|
__privateAdd(this, _Range_instances);
|
|
@@ -1989,27 +2672,64 @@ var Range = class {
|
|
|
1989
2672
|
* Gets all cell values as a 2D array (row-major order).
|
|
1990
2673
|
*/
|
|
1991
2674
|
getValues() {
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2675
|
+
const rowCount = this.rowCount;
|
|
2676
|
+
const columnCount = this.columnCount;
|
|
2677
|
+
const result = new Array(rowCount);
|
|
2678
|
+
for (let r = 0; r < rowCount; r++) result[r] = new Array(columnCount).fill(null);
|
|
2679
|
+
const startRow = __privateGet(this, _startRow);
|
|
2680
|
+
const startColumn = __privateGet(this, _startColumn);
|
|
2681
|
+
__privateGet(this, _sheet).forEachCellIn(
|
|
2682
|
+
startRow,
|
|
2683
|
+
startColumn,
|
|
2684
|
+
__privateGet(this, _endRow),
|
|
2685
|
+
__privateGet(this, _endColumn),
|
|
2686
|
+
(cell, row, column) => {
|
|
2687
|
+
result[row - startRow][column - startColumn] = cell.getValue();
|
|
1998
2688
|
}
|
|
1999
|
-
|
|
2000
|
-
}
|
|
2689
|
+
);
|
|
2001
2690
|
return result;
|
|
2002
2691
|
}
|
|
2003
2692
|
/** Clears all cells in the range (values only, preserves style). */
|
|
2004
2693
|
clear() {
|
|
2005
|
-
__privateMethod(this, _Range_instances,
|
|
2694
|
+
__privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clear());
|
|
2006
2695
|
}
|
|
2007
|
-
/**
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2696
|
+
/** Clears values AND styles of all cells in the range. */
|
|
2697
|
+
clearAll() {
|
|
2698
|
+
__privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clearAll());
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Moves the whole range so its top-left cell lands on `destTopLeft`,
|
|
2702
|
+
* carrying values, formulas, styles and spill anchors. The vacated cells
|
|
2703
|
+
* are cleared and the destination block is fully replaced (empty source
|
|
2704
|
+
* cells clear their destination). Overlapping source/destination is safe.
|
|
2705
|
+
* Formula text is NOT adjusted for the new position.
|
|
2706
|
+
*
|
|
2707
|
+
* @example
|
|
2708
|
+
* ws.getRange('A1:B3').moveTo('D1');
|
|
2709
|
+
*/
|
|
2710
|
+
moveTo(destTopLeft) {
|
|
2711
|
+
const dest = parseCellRef(destTopLeft.replace(/\$/g, "").toUpperCase());
|
|
2712
|
+
const deltaRow = dest.row - __privateGet(this, _startRow);
|
|
2713
|
+
const deltaCol = dest.column - __privateGet(this, _startColumn);
|
|
2714
|
+
if (deltaRow === 0 && deltaCol === 0) return;
|
|
2715
|
+
if (__privateGet(this, _endRow) + deltaRow > EXCEL_MAX_ROWS || __privateGet(this, _endColumn) + deltaCol > EXCEL_MAX_COLUMNS) {
|
|
2716
|
+
throw new RangeError("Destination extends past the sheet limits.");
|
|
2717
|
+
}
|
|
2718
|
+
const rowOrder = deltaRow > 0 ? { from: __privateGet(this, _endRow), to: __privateGet(this, _startRow), step: -1 } : { from: __privateGet(this, _startRow), to: __privateGet(this, _endRow), step: 1 };
|
|
2719
|
+
const colOrder = deltaCol > 0 ? { from: __privateGet(this, _endColumn), to: __privateGet(this, _startColumn), step: -1 } : { from: __privateGet(this, _startColumn), to: __privateGet(this, _endColumn), step: 1 };
|
|
2720
|
+
for (let r = rowOrder.from; deltaRow > 0 ? r >= rowOrder.to : r <= rowOrder.to; r += rowOrder.step) {
|
|
2721
|
+
for (let c = colOrder.from; deltaCol > 0 ? c >= colOrder.to : c <= colOrder.to; c += colOrder.step) {
|
|
2722
|
+
__privateGet(this, _sheet).moveCellRC(r, c, r + deltaRow, c + deltaCol);
|
|
2723
|
+
}
|
|
2011
2724
|
}
|
|
2012
2725
|
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Auto-fits all columns in the range. Measures every column in one sparse
|
|
2728
|
+
* sweep rather than re-scanning the sheet per column.
|
|
2729
|
+
*/
|
|
2730
|
+
autoFit() {
|
|
2731
|
+
__privateGet(this, _sheet).autoFitColumns(__privateGet(this, _startColumn), __privateGet(this, _endColumn));
|
|
2732
|
+
}
|
|
2013
2733
|
// ── Style bulk operations ──────────────────────────────────────────────────
|
|
2014
2734
|
/** Applies a CellStyle to all cells in the range. Returns this for chaining. */
|
|
2015
2735
|
setStyle(style) {
|
|
@@ -2082,9 +2802,57 @@ forEach_fn = function(action) {
|
|
|
2082
2802
|
}
|
|
2083
2803
|
}
|
|
2084
2804
|
};
|
|
2805
|
+
/**
|
|
2806
|
+
* Like #forEach but only visits cells that already exist — used by the
|
|
2807
|
+
* clearing operations so they never materialise empty cells, and so the cost
|
|
2808
|
+
* tracks the populated cells rather than the area of the range.
|
|
2809
|
+
*/
|
|
2810
|
+
forEachExisting_fn = function(action) {
|
|
2811
|
+
__privateGet(this, _sheet).forEachCellIn(
|
|
2812
|
+
__privateGet(this, _startRow),
|
|
2813
|
+
__privateGet(this, _startColumn),
|
|
2814
|
+
__privateGet(this, _endRow),
|
|
2815
|
+
__privateGet(this, _endColumn),
|
|
2816
|
+
(cell) => action(cell)
|
|
2817
|
+
);
|
|
2818
|
+
};
|
|
2085
2819
|
|
|
2086
2820
|
// src/model/worksheet.ts
|
|
2087
|
-
var
|
|
2821
|
+
var PROTECTION_DEFAULTS = {
|
|
2822
|
+
allowSelectLockedCells: true,
|
|
2823
|
+
allowSelectUnlockedCells: true,
|
|
2824
|
+
allowFormatCells: false,
|
|
2825
|
+
allowFormatColumns: false,
|
|
2826
|
+
allowFormatRows: false,
|
|
2827
|
+
allowInsertRows: false,
|
|
2828
|
+
allowInsertColumns: false,
|
|
2829
|
+
allowInsertHyperlinks: false,
|
|
2830
|
+
allowDeleteRows: false,
|
|
2831
|
+
allowDeleteColumns: false,
|
|
2832
|
+
allowSort: false,
|
|
2833
|
+
allowAutoFilter: false,
|
|
2834
|
+
allowPivotTables: false,
|
|
2835
|
+
allowEditObjects: false,
|
|
2836
|
+
allowEditScenarios: false
|
|
2837
|
+
};
|
|
2838
|
+
var PROTECTION_ATTRS = [
|
|
2839
|
+
["allowSelectLockedCells", "selectLockedCells"],
|
|
2840
|
+
["allowSelectUnlockedCells", "selectUnlockedCells"],
|
|
2841
|
+
["allowFormatCells", "formatCells"],
|
|
2842
|
+
["allowFormatColumns", "formatColumns"],
|
|
2843
|
+
["allowFormatRows", "formatRows"],
|
|
2844
|
+
["allowInsertRows", "insertRows"],
|
|
2845
|
+
["allowInsertColumns", "insertColumns"],
|
|
2846
|
+
["allowInsertHyperlinks", "insertHyperlinks"],
|
|
2847
|
+
["allowDeleteRows", "deleteRows"],
|
|
2848
|
+
["allowDeleteColumns", "deleteColumns"],
|
|
2849
|
+
["allowSort", "sort"],
|
|
2850
|
+
["allowAutoFilter", "autoFilter"],
|
|
2851
|
+
["allowPivotTables", "pivotTables"],
|
|
2852
|
+
["allowEditObjects", "objects"],
|
|
2853
|
+
["allowEditScenarios", "scenarios"]
|
|
2854
|
+
];
|
|
2855
|
+
var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _protection, _protectionCredentials, _hyperlinks, _comments, _commentsDirty, _pendingHyperlinkRels, _preservedTail, _preservedRels, _Worksheet_instances, takeCellData_fn, takeCellDataAt_fn, deleteCellData_fn, applyStructural_fn, shiftRowsAndCells_fn, shiftRows_fn, shiftColumns_fn, maxPopulatedRow_fn, shiftSpillRanges_fn, shiftSpillRange_fn, rewriteFormulas_fn, shiftMerges_fn, shiftAutoFilter_fn, shiftValidations_fn, shiftHyperlinks_fn, shiftComments_fn, shiftColDefs_fn, relIdBase_fn, activeTail_fn, buildTailXml_fn, buildSheetProtectionXml_fn, buildHyperlinksXml_fn, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, splitColSpan_fn, insertColDef_fn, findColDefIndex_fn, findColDef_fn, pruneColDefs_fn, getOrCreateRow_fn;
|
|
2088
2856
|
var Worksheet = class {
|
|
2089
2857
|
/** @internal */
|
|
2090
2858
|
constructor(name, sharedStrings, styles) {
|
|
@@ -2100,6 +2868,32 @@ var Worksheet = class {
|
|
|
2100
2868
|
__privateAdd(this, _dataValidations, []);
|
|
2101
2869
|
__privateAdd(this, _visibility, "visible");
|
|
2102
2870
|
__privateAdd(this, _drawings, []);
|
|
2871
|
+
__privateAdd(this, _protection);
|
|
2872
|
+
/**
|
|
2873
|
+
* Password/hash attributes of a protection element read from a file, kept
|
|
2874
|
+
* verbatim so a protected sheet round-trips with its password intact. This
|
|
2875
|
+
* library never creates them: see {@link protect}.
|
|
2876
|
+
*/
|
|
2877
|
+
__privateAdd(this, _protectionCredentials, "");
|
|
2878
|
+
/** Hyperlinks keyed by their normalised ref, in insertion order. */
|
|
2879
|
+
__privateAdd(this, _hyperlinks, /* @__PURE__ */ new Map());
|
|
2880
|
+
/** Comments keyed by their normalised cell ref, in insertion order. */
|
|
2881
|
+
__privateAdd(this, _comments, /* @__PURE__ */ new Map());
|
|
2882
|
+
/**
|
|
2883
|
+
* True once the comment set has been modified through the API. Until then a
|
|
2884
|
+
* sheet loaded from a file keeps its original comments and VML parts verbatim,
|
|
2885
|
+
* so untouched files round-trip byte-for-byte; from then on this sheet owns
|
|
2886
|
+
* them and regenerates both. See {@link setComment}.
|
|
2887
|
+
*/
|
|
2888
|
+
__privateAdd(this, _commentsDirty, false);
|
|
2889
|
+
/** Hyperlink ref → relationship id, awaiting resolution against the rels part. */
|
|
2890
|
+
__privateAdd(this, _pendingHyperlinkRels, /* @__PURE__ */ new Map());
|
|
2891
|
+
// Round-trip state captured when this sheet was loaded from a file: the
|
|
2892
|
+
// relationship-bearing leaf elements of the worksheet part, and the sheet's
|
|
2893
|
+
// original relationships. Re-emitted on save so drawings, images and legacy
|
|
2894
|
+
// comment anchors survive open→save. See io/preserved-parts.ts.
|
|
2895
|
+
__privateAdd(this, _preservedTail, []);
|
|
2896
|
+
__privateAdd(this, _preservedRels, []);
|
|
2103
2897
|
__privateSet(this, _name, name);
|
|
2104
2898
|
__privateSet(this, _sharedStrings2, sharedStrings);
|
|
2105
2899
|
__privateSet(this, _styles2, styles);
|
|
@@ -2113,8 +2907,15 @@ var Worksheet = class {
|
|
|
2113
2907
|
__privateSet(this, _name, value);
|
|
2114
2908
|
}
|
|
2115
2909
|
getCell(refOrRow, column) {
|
|
2116
|
-
|
|
2117
|
-
|
|
2910
|
+
let ref;
|
|
2911
|
+
let row;
|
|
2912
|
+
if (typeof refOrRow === "string") {
|
|
2913
|
+
ref = normalizeRefFast(refOrRow);
|
|
2914
|
+
row = parseCellRef(ref).row;
|
|
2915
|
+
} else {
|
|
2916
|
+
ref = cellRefFromRowCol(refOrRow, column);
|
|
2917
|
+
row = refOrRow;
|
|
2918
|
+
}
|
|
2118
2919
|
const rowData = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, row);
|
|
2119
2920
|
let cellData = rowData.cells.get(ref);
|
|
2120
2921
|
if (!cellData) {
|
|
@@ -2124,10 +2925,17 @@ var Worksheet = class {
|
|
|
2124
2925
|
return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
|
|
2125
2926
|
}
|
|
2126
2927
|
tryGetCell(refOrRow, column) {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2928
|
+
let rowData;
|
|
2929
|
+
let ref;
|
|
2930
|
+
if (typeof refOrRow === "string") {
|
|
2931
|
+
ref = normalizeRefFast(refOrRow);
|
|
2932
|
+
rowData = __privateGet(this, _rows).get(parseCellRef(ref).row);
|
|
2933
|
+
if (!rowData) return null;
|
|
2934
|
+
} else {
|
|
2935
|
+
rowData = __privateGet(this, _rows).get(refOrRow);
|
|
2936
|
+
if (!rowData) return null;
|
|
2937
|
+
ref = cellRefFromRowCol(refOrRow, column);
|
|
2938
|
+
}
|
|
2131
2939
|
const cellData = rowData.cells.get(ref);
|
|
2132
2940
|
if (!cellData) return null;
|
|
2133
2941
|
return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
|
|
@@ -2152,10 +2960,83 @@ var Worksheet = class {
|
|
|
2152
2960
|
getSpillRange(reference) {
|
|
2153
2961
|
return this.tryGetCell(reference)?.getArrayRef() ?? null;
|
|
2154
2962
|
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Iterates the populated rows of the sheet in ascending row order, yielding
|
|
2965
|
+
* each row's 1-based index and its cells (left to right). Rows and cells
|
|
2966
|
+
* that were never written are skipped, so a sparse sheet iterates in
|
|
2967
|
+
* O(populated cells) regardless of its bounds.
|
|
2968
|
+
*
|
|
2969
|
+
* @example
|
|
2970
|
+
* for (const { rowIndex, cells } of ws.rows()) {
|
|
2971
|
+
* for (const cell of cells) console.log(rowIndex, cell.reference, cell.getValue());
|
|
2972
|
+
* }
|
|
2973
|
+
*/
|
|
2974
|
+
*rows() {
|
|
2975
|
+
const populated = [...__privateGet(this, _rows).values()].filter((row) => row.cells.size > 0).sort((a, b) => a.rowIndex - b.rowIndex);
|
|
2976
|
+
for (const row of populated) {
|
|
2977
|
+
const cells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col).map(({ cd }) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)));
|
|
2978
|
+
yield { rowIndex: row.rowIndex, cells };
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Iterates every populated cell of the sheet in row-major order. Sparse
|
|
2983
|
+
* counterpart to scanning {@link usedBounds} with {@link tryGetCell}.
|
|
2984
|
+
*
|
|
2985
|
+
* @example
|
|
2986
|
+
* for (const cell of ws.cells()) console.log(cell.reference, cell.getValue());
|
|
2987
|
+
*/
|
|
2988
|
+
*cells() {
|
|
2989
|
+
for (const { cells } of this.rows()) yield* cells;
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* @internal Visits the populated cells inside a rectangle, passing the
|
|
2993
|
+
* coordinates it already knows so the callback never has to parse a
|
|
2994
|
+
* reference. Backs the Range operations that must not create cells; probing
|
|
2995
|
+
* every coordinate would cost O(area) even on an empty sheet.
|
|
2996
|
+
*/
|
|
2997
|
+
forEachCellIn(startRow, startColumn, endRow, endColumn, action) {
|
|
2998
|
+
const visit = (row) => {
|
|
2999
|
+
for (const cd of row.cells.values()) {
|
|
3000
|
+
const col = columnFromRef(cd.reference, refSplit(cd.reference));
|
|
3001
|
+
if (col < startColumn || col > endColumn) continue;
|
|
3002
|
+
action(new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)), row.rowIndex, col);
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
if (endRow - startRow + 1 <= __privateGet(this, _rows).size) {
|
|
3006
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
3007
|
+
const row = __privateGet(this, _rows).get(r);
|
|
3008
|
+
if (row) visit(row);
|
|
3009
|
+
}
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3013
|
+
if (row.rowIndex >= startRow && row.rowIndex <= endRow) visit(row);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
/**
|
|
3017
|
+
* @internal Moves a cell by coordinates, skipping the reference parsing that
|
|
3018
|
+
* the public {@link moveCell} has to do. Used by bulk range moves.
|
|
3019
|
+
*/
|
|
3020
|
+
moveCellRC(fromRow, fromColumn, toRow, toColumn) {
|
|
3021
|
+
if (fromRow === toRow && fromColumn === toColumn) return;
|
|
3022
|
+
const fromRef = cellRefFromRowCol(fromRow, fromColumn);
|
|
3023
|
+
const toRef = cellRefFromRowCol(toRow, toColumn);
|
|
3024
|
+
const src = __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, fromRow, fromRef);
|
|
3025
|
+
if (!src) {
|
|
3026
|
+
__privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, toRow, toRef);
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
const moved = { ...src, reference: toRef };
|
|
3030
|
+
if (moved.arrayRef) {
|
|
3031
|
+
moved.arrayRef = translateRangeRef(moved.arrayRef, toRow - fromRow, toColumn - fromColumn);
|
|
3032
|
+
}
|
|
3033
|
+
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toRow).cells.set(toRef, moved);
|
|
3034
|
+
}
|
|
2155
3035
|
/**
|
|
2156
3036
|
* Returns the 1-based extent of populated cells (largest row and column that
|
|
2157
3037
|
* contain data). Returns zeros for an empty sheet. Useful for snapshotting a
|
|
2158
|
-
* sheet into a dense 2D array
|
|
3038
|
+
* sheet into a dense 2D array — though {@link rows} and {@link cells} are
|
|
3039
|
+
* cheaper when a dense shape is not actually required.
|
|
2159
3040
|
*/
|
|
2160
3041
|
get usedBounds() {
|
|
2161
3042
|
let rowCount = 0;
|
|
@@ -2164,7 +3045,7 @@ var Worksheet = class {
|
|
|
2164
3045
|
if (row.cells.size === 0) continue;
|
|
2165
3046
|
if (row.rowIndex > rowCount) rowCount = row.rowIndex;
|
|
2166
3047
|
for (const ref of row.cells.keys()) {
|
|
2167
|
-
const col =
|
|
3048
|
+
const col = columnFromRef(ref, refSplit(ref));
|
|
2168
3049
|
if (col > columnCount) columnCount = col;
|
|
2169
3050
|
}
|
|
2170
3051
|
}
|
|
@@ -2175,24 +3056,72 @@ var Worksheet = class {
|
|
|
2175
3056
|
setColumnWidth(columnIndex, width) {
|
|
2176
3057
|
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
2177
3058
|
if (width <= 0) throw new RangeError("Width must be > 0.");
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
3059
|
+
const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex) ?? __privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false });
|
|
3060
|
+
def.width = width;
|
|
3061
|
+
def.customWidth = true;
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* Removes the explicit width of a column so it falls back to the sheet
|
|
3065
|
+
* default. Counterpart to {@link setColumnWidth}; afterwards
|
|
3066
|
+
* {@link getColumnWidth} returns `undefined`. A hidden column stays hidden.
|
|
3067
|
+
*/
|
|
3068
|
+
resetColumnWidth(columnIndex) {
|
|
3069
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3070
|
+
const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
|
|
3071
|
+
if (!def) return;
|
|
3072
|
+
def.width = void 0;
|
|
3073
|
+
def.customWidth = false;
|
|
3074
|
+
__privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Shows or hides a column (1-based index). Hidden columns round-trip to the
|
|
3078
|
+
* file as `<col hidden="1">` and keep any explicit width.
|
|
3079
|
+
*/
|
|
3080
|
+
setColumnHidden(columnIndex, hidden = true) {
|
|
3081
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3082
|
+
const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
|
|
3083
|
+
if (def) {
|
|
3084
|
+
def.hidden = hidden;
|
|
3085
|
+
__privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
|
|
3086
|
+
} else if (hidden) {
|
|
3087
|
+
__privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false, hidden: true });
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
/** True if the given 1-based column is hidden. */
|
|
3091
|
+
isColumnHidden(columnIndex) {
|
|
3092
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3093
|
+
return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.hidden === true;
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Returns the explicit width of a column (1-based index), or `undefined` when
|
|
3097
|
+
* the column uses the sheet default. Counterpart to {@link setColumnWidth};
|
|
3098
|
+
* widths parsed from an existing file are visible here too, so column layout
|
|
3099
|
+
* can be round-tripped.
|
|
3100
|
+
*/
|
|
3101
|
+
getColumnWidth(columnIndex) {
|
|
3102
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3103
|
+
return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.width;
|
|
2181
3104
|
}
|
|
2182
3105
|
/** Auto-fits column width based on content (approximate). */
|
|
2183
3106
|
autoFitColumn(columnIndex) {
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
3107
|
+
this.autoFitColumns(columnIndex, columnIndex);
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Auto-fits an inclusive span of columns (1-based) in a single pass over the
|
|
3111
|
+
* populated cells, instead of re-scanning the sheet once per column.
|
|
3112
|
+
*/
|
|
3113
|
+
autoFitColumns(firstColumn, lastColumn) {
|
|
3114
|
+
if (firstColumn < 1) throw new RangeError("Column index must be >= 1.");
|
|
3115
|
+
if (lastColumn < firstColumn) return;
|
|
3116
|
+
const maxLen = /* @__PURE__ */ new Map();
|
|
3117
|
+
this.forEachCellIn(1, firstColumn, EXCEL_MAX_ROWS, lastColumn, (cell, _row, column) => {
|
|
3118
|
+
const len = cell.getText().length;
|
|
3119
|
+
if (len > (maxLen.get(column) ?? 0)) maxLen.set(column, len);
|
|
3120
|
+
});
|
|
3121
|
+
for (let col = firstColumn; col <= lastColumn; col++) {
|
|
3122
|
+
const len = Math.max(maxLen.get(col) ?? 0, 8);
|
|
3123
|
+
this.setColumnWidth(col, Math.min(len * 1.2 + 2, 255));
|
|
2194
3124
|
}
|
|
2195
|
-
this.setColumnWidth(columnIndex, Math.min(maxLen * 1.2 + 2, 255));
|
|
2196
3125
|
}
|
|
2197
3126
|
/** Sets the height of a row (1-based index). */
|
|
2198
3127
|
setRowHeight(rowIndex, height) {
|
|
@@ -2202,6 +3131,27 @@ var Worksheet = class {
|
|
|
2202
3131
|
row.height = height;
|
|
2203
3132
|
row.customHeight = true;
|
|
2204
3133
|
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Returns the explicit height of a row (1-based index), or `undefined` when
|
|
3136
|
+
* the row uses the sheet default. Counterpart to {@link setRowHeight}.
|
|
3137
|
+
*/
|
|
3138
|
+
getRowHeight(rowIndex) {
|
|
3139
|
+
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
3140
|
+
return __privateGet(this, _rows).get(rowIndex)?.height;
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Removes the explicit height of a row so it falls back to the sheet
|
|
3144
|
+
* default. Counterpart to {@link setRowHeight}; afterwards
|
|
3145
|
+
* {@link getRowHeight} returns `undefined`. A hidden row stays hidden.
|
|
3146
|
+
*/
|
|
3147
|
+
resetRowHeight(rowIndex) {
|
|
3148
|
+
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
3149
|
+
const row = __privateGet(this, _rows).get(rowIndex);
|
|
3150
|
+
if (!row) return;
|
|
3151
|
+
row.height = void 0;
|
|
3152
|
+
row.customHeight = void 0;
|
|
3153
|
+
if (row.cells.size === 0 && !row.hidden) __privateGet(this, _rows).delete(rowIndex);
|
|
3154
|
+
}
|
|
2205
3155
|
/** Shows or hides a row (1-based index). Hidden rows affect SUBTOTAL(101-111). */
|
|
2206
3156
|
setRowHidden(rowIndex, hidden = true) {
|
|
2207
3157
|
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
@@ -2211,6 +3161,137 @@ var Worksheet = class {
|
|
|
2211
3161
|
isRowHidden(rowIndex) {
|
|
2212
3162
|
return __privateGet(this, _rows).get(rowIndex)?.hidden === true;
|
|
2213
3163
|
}
|
|
3164
|
+
/**
|
|
3165
|
+
* The 1-based indices of every hidden row, ascending. Sparse counterpart to
|
|
3166
|
+
* probing {@link isRowHidden} across {@link usedBounds}.
|
|
3167
|
+
*/
|
|
3168
|
+
get hiddenRows() {
|
|
3169
|
+
const result = [];
|
|
3170
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3171
|
+
if (row.hidden) result.push(row.rowIndex);
|
|
3172
|
+
}
|
|
3173
|
+
return result.sort((a, b) => a - b);
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* The 1-based indices of every hidden column, ascending. Counterpart to
|
|
3177
|
+
* {@link isColumnHidden}, in the same spirit as {@link hiddenRows}.
|
|
3178
|
+
*/
|
|
3179
|
+
get hiddenColumns() {
|
|
3180
|
+
const result = [];
|
|
3181
|
+
for (const def of __privateGet(this, _cols)) {
|
|
3182
|
+
if (!def.hidden) continue;
|
|
3183
|
+
for (let c = def.min; c <= def.max; c++) result.push(c);
|
|
3184
|
+
}
|
|
3185
|
+
return result.sort((a, b) => a - b);
|
|
3186
|
+
}
|
|
3187
|
+
// ── Structural editing ─────────────────────────────────────────────────────
|
|
3188
|
+
/**
|
|
3189
|
+
* Inserts `count` empty rows starting at row `at` (1-based). Everything at
|
|
3190
|
+
* or below `at` moves down: cell values, formulas (references are rewritten
|
|
3191
|
+
* the way Excel does), styles, row heights, hidden flags, merges,
|
|
3192
|
+
* autofilter and data-validation ranges.
|
|
3193
|
+
*
|
|
3194
|
+
* Frozen panes are a view property and stay put. Drawing/chart anchors
|
|
3195
|
+
* preserved from a loaded file are opaque XML and do NOT shift — images
|
|
3196
|
+
* keep their absolute position (known limitation).
|
|
3197
|
+
*
|
|
3198
|
+
* When formulas on OTHER sheets reference this sheet, use the
|
|
3199
|
+
* Workbook-level counterpart so they are rewritten too, or call
|
|
3200
|
+
* `applyExternalShift` on each sheet yourself. If a RecalcEngine is
|
|
3201
|
+
* attached, notify it afterwards (e.g. `engine.onRowsInserted(...)`).
|
|
3202
|
+
*
|
|
3203
|
+
* @example
|
|
3204
|
+
* ws.insertRows(3, 2); // pushes row 3 and below down by two rows
|
|
3205
|
+
*/
|
|
3206
|
+
insertRows(at, count = 1) {
|
|
3207
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "insert", at, count);
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Deletes `count` rows starting at row `at` (1-based). Rows below move up;
|
|
3211
|
+
* formulas referencing the deleted band get `#REF!` (fully inside) or
|
|
3212
|
+
* shrink (partially inside), exactly like Excel. See {@link insertRows}
|
|
3213
|
+
* for the shifting rules and known limitations.
|
|
3214
|
+
*/
|
|
3215
|
+
deleteRows(at, count = 1) {
|
|
3216
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "delete", at, count);
|
|
3217
|
+
}
|
|
3218
|
+
/**
|
|
3219
|
+
* Inserts `count` empty columns starting at column `at` (1-based).
|
|
3220
|
+
* Column-level counterpart to {@link insertRows}; widths and hidden flags
|
|
3221
|
+
* shift with the columns.
|
|
3222
|
+
*/
|
|
3223
|
+
insertColumns(at, count = 1) {
|
|
3224
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "insert", at, count);
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Deletes `count` columns starting at column `at` (1-based).
|
|
3228
|
+
* Column-level counterpart to {@link deleteRows}.
|
|
3229
|
+
*/
|
|
3230
|
+
deleteColumns(at, count = 1) {
|
|
3231
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "delete", at, count);
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* @internal Rewrites formulas on THIS sheet after a structural edit on a
|
|
3235
|
+
* DIFFERENT sheet (named by `shift.sheetName`), so qualified references
|
|
3236
|
+
* like `'Data'!A5` stay correct. Cell positions here do not change.
|
|
3237
|
+
*/
|
|
3238
|
+
applyExternalShift(shift) {
|
|
3239
|
+
return { refErrors: __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift) };
|
|
3240
|
+
}
|
|
3241
|
+
/**
|
|
3242
|
+
* Moves a cell's full contents — value, formula, style and spill anchor —
|
|
3243
|
+
* to another cell, overwriting the destination and clearing the source.
|
|
3244
|
+
* A missing source clears the destination. This is a literal move: formula
|
|
3245
|
+
* text is NOT adjusted for the new position.
|
|
3246
|
+
*
|
|
3247
|
+
* @example
|
|
3248
|
+
* ws.moveCell('A1', 'C5');
|
|
3249
|
+
*/
|
|
3250
|
+
moveCell(from, to) {
|
|
3251
|
+
const fromRef = normalizeRef(from);
|
|
3252
|
+
const toRef = normalizeRef(to);
|
|
3253
|
+
if (fromRef === toRef) return;
|
|
3254
|
+
const src = __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, fromRef);
|
|
3255
|
+
if (!src) {
|
|
3256
|
+
__privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
const fromPos = parseCellRef(fromRef);
|
|
3260
|
+
const toPos = parseCellRef(toRef);
|
|
3261
|
+
const moved = { ...src, reference: toRef };
|
|
3262
|
+
if (moved.arrayRef) {
|
|
3263
|
+
moved.arrayRef = translateRangeRef(
|
|
3264
|
+
moved.arrayRef,
|
|
3265
|
+
toPos.row - fromPos.row,
|
|
3266
|
+
toPos.column - fromPos.column
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, moved);
|
|
3270
|
+
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Copies a cell's value, formula and style to another cell, overwriting the
|
|
3273
|
+
* destination and leaving the source untouched. A missing source clears the
|
|
3274
|
+
* destination. This is a literal copy: formula references are NOT adjusted,
|
|
3275
|
+
* and the spill anchor (`arrayRef`) is not copied — a recalc engine
|
|
3276
|
+
* re-establishes it.
|
|
3277
|
+
*
|
|
3278
|
+
* @example
|
|
3279
|
+
* ws.copyCell('A1', 'C5');
|
|
3280
|
+
*/
|
|
3281
|
+
copyCell(from, to) {
|
|
3282
|
+
const fromRef = normalizeRef(from);
|
|
3283
|
+
const toRef = normalizeRef(to);
|
|
3284
|
+
if (fromRef === toRef) return;
|
|
3285
|
+
const srcRow = __privateGet(this, _rows).get(parseCellRef(fromRef).row);
|
|
3286
|
+
const src = srcRow?.cells.get(fromRef);
|
|
3287
|
+
if (!src) {
|
|
3288
|
+
__privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
const toPos = parseCellRef(toRef);
|
|
3292
|
+
const copied = { ...src, reference: toRef, arrayRef: void 0 };
|
|
3293
|
+
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, copied);
|
|
3294
|
+
}
|
|
2214
3295
|
/** Merges cells in the given range (e.g. "A1:B2"). */
|
|
2215
3296
|
mergeCells(rangeReference) {
|
|
2216
3297
|
if (!rangeReference) throw new Error("Range reference cannot be empty.");
|
|
@@ -2220,6 +3301,14 @@ var Worksheet = class {
|
|
|
2220
3301
|
unmergeCells(rangeReference) {
|
|
2221
3302
|
__privateSet(this, _mergeCells, __privateGet(this, _mergeCells).filter((m) => m.ref !== rangeReference));
|
|
2222
3303
|
}
|
|
3304
|
+
/**
|
|
3305
|
+
* All merged ranges on this sheet, in the order they were added (or parsed).
|
|
3306
|
+
* Includes merges read from an existing file, so callers never need to track
|
|
3307
|
+
* their own copy of the list.
|
|
3308
|
+
*/
|
|
3309
|
+
get merges() {
|
|
3310
|
+
return __privateGet(this, _mergeCells).map((m) => m.ref);
|
|
3311
|
+
}
|
|
2223
3312
|
/**
|
|
2224
3313
|
* Freezes rows and/or columns.
|
|
2225
3314
|
* @param row Number of rows to freeze (0 = none)
|
|
@@ -2249,6 +3338,119 @@ var Worksheet = class {
|
|
|
2249
3338
|
get visibility() {
|
|
2250
3339
|
return __privateGet(this, _visibility);
|
|
2251
3340
|
}
|
|
3341
|
+
// ── Sheet protection ───────────────────────────────────────────────────────
|
|
3342
|
+
/**
|
|
3343
|
+
* Turns on sheet protection, optionally relaxing individual permissions.
|
|
3344
|
+
* Cells are locked by default in Excel, so protecting a sheet makes all of
|
|
3345
|
+
* them read-only; clear a cell style's `locked` flag to leave a cell editable.
|
|
3346
|
+
*
|
|
3347
|
+
* Excel's sheet protection is a UI guard, **not** security: the contents stay
|
|
3348
|
+
* readable to anything that opens the file. This library therefore never
|
|
3349
|
+
* creates a protection password. A password read from an existing file is
|
|
3350
|
+
* preserved so the sheet round-trips unchanged.
|
|
3351
|
+
*
|
|
3352
|
+
* @example
|
|
3353
|
+
* ws.protect(); // lock everything
|
|
3354
|
+
* ws.protect({ allowSort: true, allowAutoFilter: true });
|
|
3355
|
+
*/
|
|
3356
|
+
protect(options) {
|
|
3357
|
+
__privateSet(this, _protection, { ...PROTECTION_DEFAULTS, ...options });
|
|
3358
|
+
}
|
|
3359
|
+
/** Turns off sheet protection, discarding any password read from a file. */
|
|
3360
|
+
unprotect() {
|
|
3361
|
+
__privateSet(this, _protection, void 0);
|
|
3362
|
+
__privateSet(this, _protectionCredentials, "");
|
|
3363
|
+
}
|
|
3364
|
+
/** True when this sheet is protected. */
|
|
3365
|
+
get isProtected() {
|
|
3366
|
+
return __privateGet(this, _protection) !== void 0;
|
|
3367
|
+
}
|
|
3368
|
+
/** The active permissions, or `undefined` when the sheet is not protected. */
|
|
3369
|
+
get protection() {
|
|
3370
|
+
return __privateGet(this, _protection) ? { ...__privateGet(this, _protection) } : void 0;
|
|
3371
|
+
}
|
|
3372
|
+
// ── Hyperlinks ─────────────────────────────────────────────────────────────
|
|
3373
|
+
/**
|
|
3374
|
+
* Attaches a hyperlink to a cell or range. Give `target` for an external
|
|
3375
|
+
* destination or `location` for one inside the workbook; passing both keeps
|
|
3376
|
+
* the external target and uses the location as its fragment, which is how
|
|
3377
|
+
* Excel links to an anchor within a document.
|
|
3378
|
+
*
|
|
3379
|
+
* A cell's displayed text still comes from its own value — set that
|
|
3380
|
+
* separately; `display` only overrides what Excel shows in the edit bar.
|
|
3381
|
+
*
|
|
3382
|
+
* @example
|
|
3383
|
+
* ws.getCell('A1').setValue('Anthropic');
|
|
3384
|
+
* ws.setHyperlink('A1', { target: 'https://anthropic.com', tooltip: 'Open site' });
|
|
3385
|
+
* ws.setHyperlink('B1', { location: "'Sheet2'!A1" });
|
|
3386
|
+
*/
|
|
3387
|
+
setHyperlink(ref, link) {
|
|
3388
|
+
if (!link.target && !link.location) {
|
|
3389
|
+
throw new Error("A hyperlink needs a target (external) or a location (in-workbook).");
|
|
3390
|
+
}
|
|
3391
|
+
const normalized = normalizeRangeRef(ref);
|
|
3392
|
+
__privateGet(this, _hyperlinks).set(normalized, { ref: normalized, ...link });
|
|
3393
|
+
}
|
|
3394
|
+
/** The hyperlink covering exactly this ref, or undefined. */
|
|
3395
|
+
getHyperlink(ref) {
|
|
3396
|
+
return __privateGet(this, _hyperlinks).get(normalizeRangeRef(ref));
|
|
3397
|
+
}
|
|
3398
|
+
/** Removes the hyperlink on this ref. Returns true if one was there. */
|
|
3399
|
+
removeHyperlink(ref) {
|
|
3400
|
+
return __privateGet(this, _hyperlinks).delete(normalizeRangeRef(ref));
|
|
3401
|
+
}
|
|
3402
|
+
/** Every hyperlink on the sheet, including ones read from a file. */
|
|
3403
|
+
get hyperlinks() {
|
|
3404
|
+
return [...__privateGet(this, _hyperlinks).values()];
|
|
3405
|
+
}
|
|
3406
|
+
// ── Comments (notes) ───────────────────────────────────────────────────────
|
|
3407
|
+
/**
|
|
3408
|
+
* Attaches a comment (a "note" in current Excel versions) to a cell,
|
|
3409
|
+
* replacing any comment already there.
|
|
3410
|
+
*
|
|
3411
|
+
* Note that writing comments makes this sheet regenerate its comment and
|
|
3412
|
+
* legacy-drawing parts on save. If the file was opened with other legacy
|
|
3413
|
+
* drawing content on the same sheet — form controls, for instance — that
|
|
3414
|
+
* content is not reproduced. Reading comments has no such effect.
|
|
3415
|
+
*
|
|
3416
|
+
* @example
|
|
3417
|
+
* ws.setComment('B4', 'Check this figure', { author: 'Bill' });
|
|
3418
|
+
*/
|
|
3419
|
+
setComment(ref, text, options) {
|
|
3420
|
+
const normalized = normalizeRef(ref);
|
|
3421
|
+
__privateGet(this, _comments).set(normalized, {
|
|
3422
|
+
ref: normalized,
|
|
3423
|
+
text,
|
|
3424
|
+
author: options?.author ?? ""
|
|
3425
|
+
});
|
|
3426
|
+
__privateSet(this, _commentsDirty, true);
|
|
3427
|
+
}
|
|
3428
|
+
/** The comment on a cell, or undefined. */
|
|
3429
|
+
getComment(ref) {
|
|
3430
|
+
return __privateGet(this, _comments).get(normalizeRef(ref));
|
|
3431
|
+
}
|
|
3432
|
+
/** Removes the comment on a cell. Returns true if one was there. */
|
|
3433
|
+
removeComment(ref) {
|
|
3434
|
+
const removed = __privateGet(this, _comments).delete(normalizeRef(ref));
|
|
3435
|
+
if (removed) __privateSet(this, _commentsDirty, true);
|
|
3436
|
+
return removed;
|
|
3437
|
+
}
|
|
3438
|
+
/** Every comment on the sheet, including ones read from a file. */
|
|
3439
|
+
get comments() {
|
|
3440
|
+
return [...__privateGet(this, _comments).values()];
|
|
3441
|
+
}
|
|
3442
|
+
/** @internal True when this sheet must write its own comment parts. */
|
|
3443
|
+
get ownsCommentParts() {
|
|
3444
|
+
return __privateGet(this, _commentsDirty) && __privateGet(this, _comments).size > 0;
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* @internal True when the sheet took over its comment parts and any preserved
|
|
3448
|
+
* originals must be dropped — including the case where every comment was
|
|
3449
|
+
* deleted and nothing replaces them.
|
|
3450
|
+
*/
|
|
3451
|
+
get commentPartsReplaced() {
|
|
3452
|
+
return __privateGet(this, _commentsDirty);
|
|
3453
|
+
}
|
|
2252
3454
|
// ── Drawings (charts, images …) ──────────────────────────────────────────────
|
|
2253
3455
|
/**
|
|
2254
3456
|
* Attaches a drawing provider (e.g. a chart) to this worksheet.
|
|
@@ -2258,6 +3460,18 @@ var Worksheet = class {
|
|
|
2258
3460
|
addDrawingProvider(provider) {
|
|
2259
3461
|
__privateGet(this, _drawings).push(provider);
|
|
2260
3462
|
}
|
|
3463
|
+
/**
|
|
3464
|
+
* Detaches every drawing from this worksheet — both providers added in memory
|
|
3465
|
+
* and any drawing preserved from the file the workbook was opened from.
|
|
3466
|
+
*
|
|
3467
|
+
* Call before re-adding charts when exporting the same in-memory workbook
|
|
3468
|
+
* repeatedly, otherwise each export appends another copy; call on its own to
|
|
3469
|
+
* deliberately drop a chart that came from the source file.
|
|
3470
|
+
*/
|
|
3471
|
+
clearDrawings() {
|
|
3472
|
+
__privateSet(this, _drawings, []);
|
|
3473
|
+
__privateSet(this, _preservedTail, __privateGet(this, _preservedTail).filter((t) => t.tag !== "drawing"));
|
|
3474
|
+
}
|
|
2261
3475
|
/** True when this worksheet has at least one drawing to serialise. */
|
|
2262
3476
|
get hasDrawings() {
|
|
2263
3477
|
return __privateGet(this, _drawings).length > 0;
|
|
@@ -2266,21 +3480,116 @@ var Worksheet = class {
|
|
|
2266
3480
|
get drawingProviders() {
|
|
2267
3481
|
return __privateGet(this, _drawings);
|
|
2268
3482
|
}
|
|
3483
|
+
// ── Round-trip preservation (internal) ───────────────────────────────────────
|
|
3484
|
+
/**
|
|
3485
|
+
* @internal Relationships carried over from the source file, minus any whose
|
|
3486
|
+
* leaf element is no longer emitted (a drawing replaced by live providers, or
|
|
3487
|
+
* cleared outright). The save pipeline writes these into the sheet's rels.
|
|
3488
|
+
*/
|
|
3489
|
+
get preservedRelationships() {
|
|
3490
|
+
const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
|
|
3491
|
+
return __privateGet(this, _preservedRels).filter((r) => {
|
|
3492
|
+
if (r.type === REL_TYPES.comments) {
|
|
3493
|
+
return !this.commentPartsReplaced && __privateGet(this, _comments).size > 0;
|
|
3494
|
+
}
|
|
3495
|
+
return liveIds.has(r.id);
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
/**
|
|
3499
|
+
* @internal Package paths of relationships that were preserved at load time
|
|
3500
|
+
* but are no longer referenced — a drawing replaced by live providers, or one
|
|
3501
|
+
* cleared outright. The save pipeline prunes these parts so the output has no
|
|
3502
|
+
* orphans.
|
|
3503
|
+
*/
|
|
3504
|
+
get droppedPreservedTargets() {
|
|
3505
|
+
const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
|
|
3506
|
+
return __privateGet(this, _preservedRels).filter((r) => !liveIds.has(r.id) && r.resolved !== void 0).map((r) => r.resolved);
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* @internal Relationship id for a drawing built from live providers. Chosen to
|
|
3510
|
+
* clear every preserved id so the two sets cannot collide.
|
|
3511
|
+
*/
|
|
3512
|
+
get drawingRelId() {
|
|
3513
|
+
return this.generatedRels().drawing ?? `rId${__privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this) + 1}`;
|
|
3514
|
+
}
|
|
3515
|
+
/**
|
|
3516
|
+
* @internal Relationship ids for the parts this sheet generates itself.
|
|
3517
|
+
*
|
|
3518
|
+
* Derived purely from the model so the worksheet XML and the sheet's rels part
|
|
3519
|
+
* — which are written by different code — cannot disagree about an id. Order is
|
|
3520
|
+
* fixed: drawing, then external hyperlinks in insertion order, then the
|
|
3521
|
+
* comments part and its legacy VML drawing.
|
|
3522
|
+
*/
|
|
3523
|
+
generatedRels() {
|
|
3524
|
+
let next = __privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this);
|
|
3525
|
+
const result = { hyperlinks: /* @__PURE__ */ new Map() };
|
|
3526
|
+
if (this.hasDrawings) result.drawing = `rId${++next}`;
|
|
3527
|
+
for (const link of __privateGet(this, _hyperlinks).values()) {
|
|
3528
|
+
if (link.target) result.hyperlinks.set(link.ref, `rId${++next}`);
|
|
3529
|
+
}
|
|
3530
|
+
if (this.ownsCommentParts) {
|
|
3531
|
+
result.comments = `rId${++next}`;
|
|
3532
|
+
result.vmlDrawing = `rId${++next}`;
|
|
3533
|
+
}
|
|
3534
|
+
return result;
|
|
3535
|
+
}
|
|
3536
|
+
/** @internal Captures round-trip state from the source package. */
|
|
3537
|
+
loadPreservedState(worksheetXml, relationships) {
|
|
3538
|
+
__privateSet(this, _preservedRels, relationships.filter((r) => r.type !== REL_TYPES.hyperlink));
|
|
3539
|
+
const known = new Set(__privateGet(this, _preservedRels).map((r) => r.id));
|
|
3540
|
+
__privateSet(this, _preservedTail, parseTailElements(worksheetXml).filter((t) => known.has(t.relId)));
|
|
3541
|
+
const byId = new Map(relationships.map((r) => [r.id, r]));
|
|
3542
|
+
for (const [ref, relId] of __privateGet(this, _pendingHyperlinkRels)) {
|
|
3543
|
+
const rel = byId.get(relId);
|
|
3544
|
+
const link = __privateGet(this, _hyperlinks).get(ref);
|
|
3545
|
+
if (!rel || !link) continue;
|
|
3546
|
+
__privateGet(this, _hyperlinks).set(ref, { ...link, target: rel.target });
|
|
3547
|
+
}
|
|
3548
|
+
__privateGet(this, _pendingHyperlinkRels).clear();
|
|
3549
|
+
for (const [ref, link] of [...__privateGet(this, _hyperlinks)]) {
|
|
3550
|
+
if (!link.target && !link.location) __privateGet(this, _hyperlinks).delete(ref);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
/**
|
|
3554
|
+
* @internal Package paths of the comment parts this sheet read at load time.
|
|
3555
|
+
* The save pipeline drops them from the preserved set once the sheet takes
|
|
3556
|
+
* ownership, so they are not written twice.
|
|
3557
|
+
*/
|
|
3558
|
+
commentPartPaths() {
|
|
3559
|
+
const paths = [];
|
|
3560
|
+
for (const rel of __privateGet(this, _preservedRels)) {
|
|
3561
|
+
if (!rel.resolved) continue;
|
|
3562
|
+
if (rel.type === REL_TYPES.comments || rel.type === REL_TYPES.vmlDrawing) {
|
|
3563
|
+
paths.push(rel.resolved);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
return paths;
|
|
3567
|
+
}
|
|
3568
|
+
/**
|
|
3569
|
+
* @internal The relationship pointing at this sheet's comments part, if the
|
|
3570
|
+
* sheet was loaded from a file that had one.
|
|
3571
|
+
*/
|
|
3572
|
+
commentsPartPath() {
|
|
3573
|
+
return __privateGet(this, _preservedRels).find((r) => r.type === REL_TYPES.comments)?.resolved;
|
|
3574
|
+
}
|
|
2269
3575
|
// ── XML serialisation ──────────────────────────────────────────────────────
|
|
2270
3576
|
/** Builds the xl/worksheets/sheetN.xml string. */
|
|
2271
3577
|
buildXml() {
|
|
3578
|
+
const rels = this.generatedRels();
|
|
3579
|
+
const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
|
|
2272
3580
|
const colsXml = __privateMethod(this, _Worksheet_instances, buildColsXml_fn).call(this);
|
|
2273
3581
|
const sheetDataXml = __privateMethod(this, _Worksheet_instances, buildSheetDataXml_fn).call(this);
|
|
2274
|
-
const
|
|
3582
|
+
const protectionXml = __privateMethod(this, _Worksheet_instances, buildSheetProtectionXml_fn).call(this);
|
|
2275
3583
|
const filterXml = __privateGet(this, _autoFilter) ? `<autoFilter ref="${__privateGet(this, _autoFilter).ref}"/>` : "";
|
|
3584
|
+
const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
|
|
2276
3585
|
const validXml = __privateMethod(this, _Worksheet_instances, buildDataValidationsXml_fn).call(this);
|
|
2277
|
-
const
|
|
2278
|
-
const
|
|
3586
|
+
const linksXml = __privateMethod(this, _Worksheet_instances, buildHyperlinksXml_fn).call(this, rels.hyperlinks);
|
|
3587
|
+
const tailXml = __privateMethod(this, _Worksheet_instances, buildTailXml_fn).call(this, rels);
|
|
2279
3588
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2280
3589
|
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
2281
3590
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
2282
3591
|
<sheetViews>${sheetViewXml}</sheetViews>
|
|
2283
|
-
${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${
|
|
3592
|
+
${colsXml}${sheetDataXml}${protectionXml}${filterXml}${mergeXml}${validXml}${linksXml}${tailXml}
|
|
2284
3593
|
</worksheet>`;
|
|
2285
3594
|
}
|
|
2286
3595
|
/** Parses xl/worksheets/sheetN.xml and loads cells into memory. */
|
|
@@ -2291,12 +3600,17 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${drawingXml}
|
|
|
2291
3600
|
__privateSet(this, _pane, void 0);
|
|
2292
3601
|
__privateSet(this, _autoFilter, void 0);
|
|
2293
3602
|
__privateSet(this, _dataValidations, []);
|
|
3603
|
+
__privateSet(this, _protection, void 0);
|
|
3604
|
+
__privateSet(this, _protectionCredentials, "");
|
|
3605
|
+
__privateGet(this, _hyperlinks).clear();
|
|
3606
|
+
__privateGet(this, _pendingHyperlinkRels).clear();
|
|
2294
3607
|
const root = parseXml(xml2);
|
|
2295
3608
|
for (const colEl of getElementsByTagName(root, "col")) {
|
|
3609
|
+
const widthAttr = getAttr(colEl, "width");
|
|
2296
3610
|
__privateGet(this, _cols).push({
|
|
2297
3611
|
min: parseInt(getAttr(colEl, "min") ?? "1", 10),
|
|
2298
3612
|
max: parseInt(getAttr(colEl, "max") ?? "1", 10),
|
|
2299
|
-
width: parseFloat(
|
|
3613
|
+
width: widthAttr !== void 0 ? parseFloat(widthAttr) : void 0,
|
|
2300
3614
|
customWidth: getAttr(colEl, "customWidth") === "1",
|
|
2301
3615
|
hidden: getAttr(colEl, "hidden") === "1"
|
|
2302
3616
|
});
|
|
@@ -2348,6 +3662,75 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${drawingXml}
|
|
|
2348
3662
|
const formula = f1Els[0]?.textContent ?? "";
|
|
2349
3663
|
if (sqref && formula) __privateGet(this, _dataValidations).push({ sqref, formula });
|
|
2350
3664
|
}
|
|
3665
|
+
const protEl = getElementsByTagName(root, "sheetProtection")[0];
|
|
3666
|
+
if (protEl && getAttr(protEl, "sheet") === "1") {
|
|
3667
|
+
const protection = { ...PROTECTION_DEFAULTS };
|
|
3668
|
+
for (const [key, attr] of PROTECTION_ATTRS) {
|
|
3669
|
+
const raw = getAttr(protEl, attr);
|
|
3670
|
+
if (raw !== void 0) protection[key] = raw !== "1";
|
|
3671
|
+
}
|
|
3672
|
+
__privateSet(this, _protection, protection);
|
|
3673
|
+
let credentials = "";
|
|
3674
|
+
for (const attr of ["password", "algorithmName", "hashValue", "saltValue", "spinCount"]) {
|
|
3675
|
+
const raw = getAttr(protEl, attr);
|
|
3676
|
+
if (raw !== void 0) credentials += ` ${attr}="${escXmlAttr(raw)}"`;
|
|
3677
|
+
}
|
|
3678
|
+
__privateSet(this, _protectionCredentials, credentials);
|
|
3679
|
+
}
|
|
3680
|
+
for (const hlEl of getElementsByTagName(root, "hyperlink")) {
|
|
3681
|
+
const ref = getAttr(hlEl, "ref");
|
|
3682
|
+
if (!ref) continue;
|
|
3683
|
+
const normalized = normalizeRangeRef(ref);
|
|
3684
|
+
__privateGet(this, _hyperlinks).set(normalized, {
|
|
3685
|
+
ref: normalized,
|
|
3686
|
+
location: getAttr(hlEl, "location"),
|
|
3687
|
+
display: getAttr(hlEl, "display"),
|
|
3688
|
+
tooltip: getAttr(hlEl, "tooltip")
|
|
3689
|
+
});
|
|
3690
|
+
const relId = getAttr(hlEl, "r:id") ?? getAttr(hlEl, "id");
|
|
3691
|
+
if (relId) __privateGet(this, _pendingHyperlinkRels).set(normalized, relId);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
/** @internal Parses an `xl/comments*.xml` part into this sheet's comments. */
|
|
3695
|
+
loadCommentsXml(xml2) {
|
|
3696
|
+
const root = parseXml(xml2);
|
|
3697
|
+
const authors = getElementsByTagName(root, "author").map((a) => a.textContent);
|
|
3698
|
+
for (const cEl of getElementsByTagName(root, "comment")) {
|
|
3699
|
+
const ref = getAttr(cEl, "ref");
|
|
3700
|
+
if (!ref) continue;
|
|
3701
|
+
const authorId = parseInt(getAttr(cEl, "authorId") ?? "0", 10);
|
|
3702
|
+
const text = getElementsByTagName(cEl, "t").map((t) => t.textContent).join("");
|
|
3703
|
+
const normalized = normalizeRef(ref);
|
|
3704
|
+
__privateGet(this, _comments).set(normalized, {
|
|
3705
|
+
ref: normalized,
|
|
3706
|
+
text,
|
|
3707
|
+
author: authors[authorId] ?? ""
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
__privateSet(this, _commentsDirty, false);
|
|
3711
|
+
}
|
|
3712
|
+
/** @internal The comments part content, when this sheet owns it. */
|
|
3713
|
+
buildCommentsXml() {
|
|
3714
|
+
const authors = [...new Set([...__privateGet(this, _comments).values()].map((c) => c.author))];
|
|
3715
|
+
const authorsXml = authors.map((a) => `<author>${escXml2(a)}</author>`).join("");
|
|
3716
|
+
const listXml = [...__privateGet(this, _comments).values()].map(
|
|
3717
|
+
(c) => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.author)}"><text><r><t xml:space="preserve">${escXml2(c.text)}</t></r></text></comment>`
|
|
3718
|
+
).join("");
|
|
3719
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3720
|
+
<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authorsXml}</authors><commentList>${listXml}</commentList></comments>`;
|
|
3721
|
+
}
|
|
3722
|
+
/**
|
|
3723
|
+
* @internal The legacy VML drawing that positions the comment boxes. Excel
|
|
3724
|
+
* needs it to draw the note indicator and popup; the comments part alone
|
|
3725
|
+
* carries only the text.
|
|
3726
|
+
*/
|
|
3727
|
+
buildCommentsVml() {
|
|
3728
|
+
const shapes = [...__privateGet(this, _comments).values()].map((c, idx) => {
|
|
3729
|
+
const { row, column } = parseCellRef(c.ref);
|
|
3730
|
+
const anchor = `${column}, 15, ${row - 1}, 10, ${column + 2}, 15, ${row + 3}, 4`;
|
|
3731
|
+
return `<v:shape id="_x0000_s${1025 + idx}" type="#_x0000_t202" style="position:absolute;margin-left:60pt;margin-top:5pt;width:108pt;height:60pt;z-index:${idx + 1};visibility:hidden" fillcolor="#ffffe1" o:insetmode="auto"><v:fill color2="#ffffe1"/><v:shadow on="t" color="black" obscured="t"/><v:path o:connecttype="none"/><v:textbox style="mso-direction-alt:auto"><div style="text-align:left"/></v:textbox><x:ClientData ObjectType="Note"><x:MoveWithCells/><x:SizeWithCells/><x:Anchor>${anchor}</x:Anchor><x:AutoFill>False</x:AutoFill><x:Row>${row - 1}</x:Row><x:Column>${column - 1}</x:Column></x:ClientData></v:shape>`;
|
|
3732
|
+
}).join("");
|
|
3733
|
+
return `<xml xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel"><o:shapelayout v:ext="edit"><o:idmap v:ext="edit" data="1"/></o:shapelayout><v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/><v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>${shapes}</xml>`;
|
|
2351
3734
|
}
|
|
2352
3735
|
};
|
|
2353
3736
|
_sharedStrings2 = new WeakMap();
|
|
@@ -2361,13 +3744,263 @@ _autoFilter = new WeakMap();
|
|
|
2361
3744
|
_dataValidations = new WeakMap();
|
|
2362
3745
|
_visibility = new WeakMap();
|
|
2363
3746
|
_drawings = new WeakMap();
|
|
3747
|
+
_protection = new WeakMap();
|
|
3748
|
+
_protectionCredentials = new WeakMap();
|
|
3749
|
+
_hyperlinks = new WeakMap();
|
|
3750
|
+
_comments = new WeakMap();
|
|
3751
|
+
_commentsDirty = new WeakMap();
|
|
3752
|
+
_pendingHyperlinkRels = new WeakMap();
|
|
3753
|
+
_preservedTail = new WeakMap();
|
|
3754
|
+
_preservedRels = new WeakMap();
|
|
2364
3755
|
_Worksheet_instances = new WeakSet();
|
|
3756
|
+
/** Removes and returns a cell's data record, pruning an emptied bare row. */
|
|
3757
|
+
takeCellData_fn = function(ref) {
|
|
3758
|
+
return __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, parseCellRef(ref).row, ref);
|
|
3759
|
+
};
|
|
3760
|
+
takeCellDataAt_fn = function(row, ref) {
|
|
3761
|
+
const rowData = __privateGet(this, _rows).get(row);
|
|
3762
|
+
const data = rowData?.cells.get(ref);
|
|
3763
|
+
if (rowData && data) {
|
|
3764
|
+
rowData.cells.delete(ref);
|
|
3765
|
+
if (rowData.cells.size === 0 && !rowData.hidden && rowData.height === void 0) {
|
|
3766
|
+
__privateGet(this, _rows).delete(row);
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
return data;
|
|
3770
|
+
};
|
|
3771
|
+
deleteCellData_fn = function(ref) {
|
|
3772
|
+
__privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, ref);
|
|
3773
|
+
};
|
|
3774
|
+
applyStructural_fn = function(dim, kind, at, count) {
|
|
3775
|
+
const what = dim === "row" ? "Row" : "Column";
|
|
3776
|
+
if (!Number.isInteger(at) || at < 1) throw new RangeError(`${what} index must be an integer >= 1.`);
|
|
3777
|
+
if (!Number.isInteger(count) || count < 1) throw new RangeError("Count must be an integer >= 1.");
|
|
3778
|
+
const shift = { dim, kind, at, count, sheetName: __privateGet(this, _name) };
|
|
3779
|
+
if (kind === "insert") {
|
|
3780
|
+
const limit = dim === "row" ? EXCEL_MAX_ROWS : EXCEL_MAX_COLUMNS;
|
|
3781
|
+
const used = dim === "row" ? __privateMethod(this, _Worksheet_instances, maxPopulatedRow_fn).call(this) : this.usedBounds.columnCount;
|
|
3782
|
+
if (used >= at && used + count > limit) {
|
|
3783
|
+
throw new RangeError(`Insert would push populated cells past the sheet limit of ${limit}.`);
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
__privateMethod(this, _Worksheet_instances, shiftRowsAndCells_fn).call(this, shift);
|
|
3787
|
+
const refErrors = __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift);
|
|
3788
|
+
__privateMethod(this, _Worksheet_instances, shiftMerges_fn).call(this, shift);
|
|
3789
|
+
__privateMethod(this, _Worksheet_instances, shiftAutoFilter_fn).call(this, shift);
|
|
3790
|
+
__privateMethod(this, _Worksheet_instances, shiftValidations_fn).call(this, shift);
|
|
3791
|
+
__privateMethod(this, _Worksheet_instances, shiftHyperlinks_fn).call(this, shift);
|
|
3792
|
+
__privateMethod(this, _Worksheet_instances, shiftComments_fn).call(this, shift);
|
|
3793
|
+
if (dim === "col") __privateMethod(this, _Worksheet_instances, shiftColDefs_fn).call(this, shift);
|
|
3794
|
+
return { refErrors };
|
|
3795
|
+
};
|
|
3796
|
+
/**
|
|
3797
|
+
* Re-keys the row map / per-row cell maps and shifts every cell's spill range.
|
|
3798
|
+
*
|
|
3799
|
+
* Rows and columns before the shift boundary keep their keys, their reference
|
|
3800
|
+
* strings and their identity, so an edit near the end of a sheet costs
|
|
3801
|
+
* proportionally little rather than rebuilding the whole model. Spill ranges
|
|
3802
|
+
* are the one thing that must be visited everywhere, since a range belonging
|
|
3803
|
+
* to an unmoved anchor can still span the boundary — but that pass is a bare
|
|
3804
|
+
* property test per cell.
|
|
3805
|
+
*/
|
|
3806
|
+
shiftRowsAndCells_fn = function(shift) {
|
|
3807
|
+
if (shift.dim === "row") __privateMethod(this, _Worksheet_instances, shiftRows_fn).call(this, shift);
|
|
3808
|
+
else __privateMethod(this, _Worksheet_instances, shiftColumns_fn).call(this, shift);
|
|
3809
|
+
};
|
|
3810
|
+
shiftRows_fn = function(shift) {
|
|
3811
|
+
const moved = [];
|
|
3812
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3813
|
+
if (row.rowIndex < shift.at) {
|
|
3814
|
+
__privateMethod(this, _Worksheet_instances, shiftSpillRanges_fn).call(this, row, shift);
|
|
3815
|
+
continue;
|
|
3816
|
+
}
|
|
3817
|
+
const shifted = shiftIndex(row.rowIndex, shift);
|
|
3818
|
+
__privateGet(this, _rows).delete(row.rowIndex);
|
|
3819
|
+
if (shifted === null) continue;
|
|
3820
|
+
row.rowIndex = shifted;
|
|
3821
|
+
moved.push(row);
|
|
3822
|
+
}
|
|
3823
|
+
for (const row of moved) {
|
|
3824
|
+
const suffix = String(row.rowIndex);
|
|
3825
|
+
const newCells = /* @__PURE__ */ new Map();
|
|
3826
|
+
for (const cd of row.cells.values()) {
|
|
3827
|
+
const ref = cd.reference.slice(0, refSplit(cd.reference)) + suffix;
|
|
3828
|
+
cd.reference = ref;
|
|
3829
|
+
if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
|
|
3830
|
+
newCells.set(ref, cd);
|
|
3831
|
+
}
|
|
3832
|
+
row.cells = newCells;
|
|
3833
|
+
__privateGet(this, _rows).set(row.rowIndex, row);
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
shiftColumns_fn = function(shift) {
|
|
3837
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3838
|
+
let affected;
|
|
3839
|
+
for (const cd of row.cells.values()) {
|
|
3840
|
+
const ref = cd.reference;
|
|
3841
|
+
if (columnFromRef(ref, refSplit(ref)) < shift.at) {
|
|
3842
|
+
__privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
|
|
3843
|
+
continue;
|
|
3844
|
+
}
|
|
3845
|
+
(affected ?? (affected = [])).push(cd);
|
|
3846
|
+
}
|
|
3847
|
+
if (!affected) continue;
|
|
3848
|
+
for (const cd of affected) row.cells.delete(cd.reference);
|
|
3849
|
+
for (const cd of affected) {
|
|
3850
|
+
const split = refSplit(cd.reference);
|
|
3851
|
+
const shifted = shiftIndex(columnFromRef(cd.reference, split), shift);
|
|
3852
|
+
if (shifted === null) continue;
|
|
3853
|
+
const ref = columnLetter(shifted) + cd.reference.slice(split);
|
|
3854
|
+
cd.reference = ref;
|
|
3855
|
+
__privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
|
|
3856
|
+
row.cells.set(ref, cd);
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
};
|
|
3860
|
+
/** Largest 1-based row index holding a cell, or 0. O(rows), no ref parsing. */
|
|
3861
|
+
maxPopulatedRow_fn = function() {
|
|
3862
|
+
let max = 0;
|
|
3863
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3864
|
+
if (row.cells.size > 0 && row.rowIndex > max) max = row.rowIndex;
|
|
3865
|
+
}
|
|
3866
|
+
return max;
|
|
3867
|
+
};
|
|
3868
|
+
shiftSpillRanges_fn = function(row, shift) {
|
|
3869
|
+
for (const cd of row.cells.values()) __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
|
|
3870
|
+
};
|
|
3871
|
+
shiftSpillRange_fn = function(cd, shift) {
|
|
3872
|
+
if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
|
|
3873
|
+
};
|
|
3874
|
+
/** Rewrites every formula on the sheet; returns refs that now hold #REF!. */
|
|
3875
|
+
rewriteFormulas_fn = function(shift) {
|
|
3876
|
+
const refErrors = [];
|
|
3877
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3878
|
+
for (const cd of row.cells.values()) {
|
|
3879
|
+
if (!cd.formula) continue;
|
|
3880
|
+
const result = shiftFormulaRefs(cd.formula, shift, __privateGet(this, _name));
|
|
3881
|
+
if (result.changed) cd.formula = result.text;
|
|
3882
|
+
if (result.hasRefError) refErrors.push(cd.reference);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
return refErrors;
|
|
3886
|
+
};
|
|
3887
|
+
shiftMerges_fn = function(shift) {
|
|
3888
|
+
__privateSet(this, _mergeCells, __privateGet(this, _mergeCells).flatMap((m) => {
|
|
3889
|
+
const ref = shiftRangeRef(m.ref, shift);
|
|
3890
|
+
if (!ref) return [];
|
|
3891
|
+
const [a, b] = ref.split(":");
|
|
3892
|
+
if (!b || a === b) return [];
|
|
3893
|
+
return [{ ref }];
|
|
3894
|
+
}));
|
|
3895
|
+
};
|
|
3896
|
+
shiftAutoFilter_fn = function(shift) {
|
|
3897
|
+
if (!__privateGet(this, _autoFilter)) return;
|
|
3898
|
+
const ref = shiftRangeRef(__privateGet(this, _autoFilter).ref, shift);
|
|
3899
|
+
__privateSet(this, _autoFilter, ref ? { ref } : void 0);
|
|
3900
|
+
};
|
|
3901
|
+
shiftValidations_fn = function(shift) {
|
|
3902
|
+
__privateSet(this, _dataValidations, __privateGet(this, _dataValidations).flatMap((dv) => {
|
|
3903
|
+
const sqref = shiftSqref(dv.sqref, shift);
|
|
3904
|
+
if (!sqref) return [];
|
|
3905
|
+
const formula = shiftFormulaRefs(dv.formula, shift, __privateGet(this, _name)).text;
|
|
3906
|
+
return [{ sqref, formula }];
|
|
3907
|
+
}));
|
|
3908
|
+
};
|
|
3909
|
+
shiftHyperlinks_fn = function(shift) {
|
|
3910
|
+
const moved = /* @__PURE__ */ new Map();
|
|
3911
|
+
for (const link of __privateGet(this, _hyperlinks).values()) {
|
|
3912
|
+
const ref = shiftRangeRef(link.ref, shift);
|
|
3913
|
+
if (!ref) continue;
|
|
3914
|
+
moved.set(ref, { ...link, ref });
|
|
3915
|
+
}
|
|
3916
|
+
__privateSet(this, _hyperlinks, moved);
|
|
3917
|
+
};
|
|
3918
|
+
shiftComments_fn = function(shift) {
|
|
3919
|
+
const moved = /* @__PURE__ */ new Map();
|
|
3920
|
+
let changed = false;
|
|
3921
|
+
for (const comment of __privateGet(this, _comments).values()) {
|
|
3922
|
+
const ref = shiftRangeRef(comment.ref, shift);
|
|
3923
|
+
if (!ref) {
|
|
3924
|
+
changed = true;
|
|
3925
|
+
continue;
|
|
3926
|
+
}
|
|
3927
|
+
if (ref !== comment.ref) changed = true;
|
|
3928
|
+
moved.set(ref, { ...comment, ref });
|
|
3929
|
+
}
|
|
3930
|
+
__privateSet(this, _comments, moved);
|
|
3931
|
+
if (changed) __privateSet(this, _commentsDirty, true);
|
|
3932
|
+
};
|
|
3933
|
+
shiftColDefs_fn = function(shift) {
|
|
3934
|
+
__privateSet(this, _cols, __privateGet(this, _cols).flatMap((c) => {
|
|
3935
|
+
const span = shiftSpan(c.min, c.max, shift);
|
|
3936
|
+
return span ? [{ ...c, min: span.start, max: span.end }] : [];
|
|
3937
|
+
}));
|
|
3938
|
+
};
|
|
3939
|
+
/** Highest numeric preserved relationship id, so generated ids clear them all. */
|
|
3940
|
+
relIdBase_fn = function() {
|
|
3941
|
+
let max = 0;
|
|
3942
|
+
for (const r of __privateGet(this, _preservedRels)) {
|
|
3943
|
+
const n = RID_PATTERN.exec(r.id);
|
|
3944
|
+
if (n) max = Math.max(max, parseInt(n[1], 10));
|
|
3945
|
+
}
|
|
3946
|
+
return max;
|
|
3947
|
+
};
|
|
3948
|
+
/**
|
|
3949
|
+
* Preserved leaf elements that will actually be emitted. A sheet may only have
|
|
3950
|
+
* one `<drawing>`, so live providers suppress a preserved one — every consumer
|
|
3951
|
+
* (serialisation, relationships, pruning) must agree on that, hence one helper.
|
|
3952
|
+
*/
|
|
3953
|
+
activeTail_fn = function() {
|
|
3954
|
+
return __privateGet(this, _preservedTail).filter((t) => {
|
|
3955
|
+
if (t.tag === "drawing") return !this.hasDrawings;
|
|
3956
|
+
if (t.tag === "legacyDrawing") return !this.commentPartsReplaced;
|
|
3957
|
+
return true;
|
|
3958
|
+
});
|
|
3959
|
+
};
|
|
3960
|
+
/**
|
|
3961
|
+
* The relationship-bearing leaf elements, in schema order: `drawing`,
|
|
3962
|
+
* `legacyDrawing`, `legacyDrawingHF`, `picture`. A sheet may hold only one of
|
|
3963
|
+
* each, so generated content replaces the preserved element of the same tag —
|
|
3964
|
+
* live drawing providers replace a preserved `<drawing>`, and comments this
|
|
3965
|
+
* sheet now owns replace a preserved `<legacyDrawing>`.
|
|
3966
|
+
*/
|
|
3967
|
+
buildTailXml_fn = function(rels) {
|
|
3968
|
+
const preserved = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this);
|
|
3969
|
+
const byTag = (tag) => preserved.filter((t) => t.tag === tag).map((t) => t.xml).join("");
|
|
3970
|
+
const drawing = rels.drawing ? `<drawing r:id="${rels.drawing}"/>` : byTag("drawing");
|
|
3971
|
+
const legacy = rels.vmlDrawing ? `<legacyDrawing r:id="${rels.vmlDrawing}"/>` : byTag("legacyDrawing");
|
|
3972
|
+
return drawing + legacy + byTag("legacyDrawingHF") + byTag("picture");
|
|
3973
|
+
};
|
|
3974
|
+
buildSheetProtectionXml_fn = function() {
|
|
3975
|
+
if (!__privateGet(this, _protection)) return "";
|
|
3976
|
+
let attrs = ' sheet="1"';
|
|
3977
|
+
for (const [key, attr] of PROTECTION_ATTRS) {
|
|
3978
|
+
const blocked = !__privateGet(this, _protection)[key];
|
|
3979
|
+
const defaultBlocked = !PROTECTION_DEFAULTS[key];
|
|
3980
|
+
if (blocked !== defaultBlocked) attrs += ` ${attr}="${blocked ? 1 : 0}"`;
|
|
3981
|
+
}
|
|
3982
|
+
return `<sheetProtection${__privateGet(this, _protectionCredentials)}${attrs}/>`;
|
|
3983
|
+
};
|
|
3984
|
+
buildHyperlinksXml_fn = function(relIds) {
|
|
3985
|
+
if (__privateGet(this, _hyperlinks).size === 0) return "";
|
|
3986
|
+
const inner = [...__privateGet(this, _hyperlinks).values()].map((link) => {
|
|
3987
|
+
const relId = relIds.get(link.ref);
|
|
3988
|
+
const parts = [`ref="${link.ref}"`];
|
|
3989
|
+
if (relId) parts.push(`r:id="${relId}"`);
|
|
3990
|
+
if (link.location) parts.push(`location="${escXmlAttr(link.location)}"`);
|
|
3991
|
+
if (link.display) parts.push(`display="${escXmlAttr(link.display)}"`);
|
|
3992
|
+
if (link.tooltip) parts.push(`tooltip="${escXmlAttr(link.tooltip)}"`);
|
|
3993
|
+
return `<hyperlink ${parts.join(" ")}/>`;
|
|
3994
|
+
}).join("");
|
|
3995
|
+
return `<hyperlinks>${inner}</hyperlinks>`;
|
|
3996
|
+
};
|
|
2365
3997
|
// ── Private XML builders ───────────────────────────────────────────────────
|
|
2366
3998
|
buildColsXml_fn = function() {
|
|
2367
3999
|
if (__privateGet(this, _cols).length === 0) return "";
|
|
2368
4000
|
const inner = __privateGet(this, _cols).map((c) => {
|
|
4001
|
+
const width = c.width !== void 0 ? ` width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"` : "";
|
|
2369
4002
|
const hidden = c.hidden ? ' hidden="1"' : "";
|
|
2370
|
-
return `<col min="${c.min}" max="${c.max}"
|
|
4003
|
+
return `<col min="${c.min}" max="${c.max}"${width}${hidden}/>`;
|
|
2371
4004
|
}).join("");
|
|
2372
4005
|
return `<cols>${inner}</cols>`;
|
|
2373
4006
|
};
|
|
@@ -2377,12 +4010,8 @@ buildSheetDataXml_fn = function() {
|
|
|
2377
4010
|
const rowsXml = sortedRows.map((row) => {
|
|
2378
4011
|
const ht = row.height !== void 0 && row.customHeight ? ` ht="${row.height}" customHeight="1"` : "";
|
|
2379
4012
|
const hidden = row.hidden ? ' hidden="1"' : "";
|
|
2380
|
-
const sortedCells = [...row.cells.values()].sort((a, b) =>
|
|
2381
|
-
|
|
2382
|
-
const rb = parseCellRef(b.reference);
|
|
2383
|
-
return ra.column - rb.column;
|
|
2384
|
-
});
|
|
2385
|
-
const cellsXml = sortedCells.map((cd) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
|
|
4013
|
+
const sortedCells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col);
|
|
4014
|
+
const cellsXml = sortedCells.map(({ cd }) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
|
|
2386
4015
|
if (!cellsXml && !ht && !hidden) return "";
|
|
2387
4016
|
return `<row r="${row.rowIndex}"${ht}${hidden}>${cellsXml}</row>`;
|
|
2388
4017
|
}).filter(Boolean).join("");
|
|
@@ -2411,6 +4040,61 @@ buildSheetViewXml_fn = function() {
|
|
|
2411
4040
|
const ySplit = row > 0 ? ` ySplit="${row}"` : "";
|
|
2412
4041
|
return `<sheetView workbookViewId="0"><pane${xSplit}${ySplit} topLeftCell="${topLeft}" activePane="${activePane}" state="frozen"/></sheetView>`;
|
|
2413
4042
|
};
|
|
4043
|
+
/**
|
|
4044
|
+
* Returns the single-column ColDef covering `columnIndex`, splitting a wider
|
|
4045
|
+
* span into up to three pieces so the other columns keep their width/hidden
|
|
4046
|
+
* state. Returns undefined when no definition covers the column.
|
|
4047
|
+
*/
|
|
4048
|
+
splitColSpan_fn = function(columnIndex) {
|
|
4049
|
+
const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
|
|
4050
|
+
if (idx === -1) return void 0;
|
|
4051
|
+
const span = __privateGet(this, _cols)[idx];
|
|
4052
|
+
if (span.min === span.max) return span;
|
|
4053
|
+
const pieces = [];
|
|
4054
|
+
if (span.min < columnIndex) pieces.push({ ...span, max: columnIndex - 1 });
|
|
4055
|
+
const target = { ...span, min: columnIndex, max: columnIndex };
|
|
4056
|
+
pieces.push(target);
|
|
4057
|
+
if (span.max > columnIndex) pieces.push({ ...span, min: columnIndex + 1 });
|
|
4058
|
+
__privateGet(this, _cols).splice(idx, 1, ...pieces);
|
|
4059
|
+
return target;
|
|
4060
|
+
};
|
|
4061
|
+
/**
|
|
4062
|
+
* Inserts a definition keeping `#cols` sorted by `min`. Binary insertion,
|
|
4063
|
+
* because re-sorting the whole array per call makes setting widths for every
|
|
4064
|
+
* column of a wide sheet quadratic.
|
|
4065
|
+
*/
|
|
4066
|
+
insertColDef_fn = function(def) {
|
|
4067
|
+
let lo = 0;
|
|
4068
|
+
let hi = __privateGet(this, _cols).length;
|
|
4069
|
+
while (lo < hi) {
|
|
4070
|
+
const mid = lo + hi >>> 1;
|
|
4071
|
+
if (__privateGet(this, _cols)[mid].min < def.min) lo = mid + 1;
|
|
4072
|
+
else hi = mid;
|
|
4073
|
+
}
|
|
4074
|
+
__privateGet(this, _cols).splice(lo, 0, def);
|
|
4075
|
+
return def;
|
|
4076
|
+
};
|
|
4077
|
+
/** Index of the definition covering `columnIndex`, or -1. Binary search. */
|
|
4078
|
+
findColDefIndex_fn = function(columnIndex) {
|
|
4079
|
+
let lo = 0;
|
|
4080
|
+
let hi = __privateGet(this, _cols).length - 1;
|
|
4081
|
+
while (lo <= hi) {
|
|
4082
|
+
const mid = lo + hi >>> 1;
|
|
4083
|
+
const c = __privateGet(this, _cols)[mid];
|
|
4084
|
+
if (columnIndex < c.min) hi = mid - 1;
|
|
4085
|
+
else if (columnIndex > c.max) lo = mid + 1;
|
|
4086
|
+
else return mid;
|
|
4087
|
+
}
|
|
4088
|
+
return -1;
|
|
4089
|
+
};
|
|
4090
|
+
findColDef_fn = function(columnIndex) {
|
|
4091
|
+
const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
|
|
4092
|
+
return idx === -1 ? void 0 : __privateGet(this, _cols)[idx];
|
|
4093
|
+
};
|
|
4094
|
+
/** Drops definitions that no longer carry any information. */
|
|
4095
|
+
pruneColDefs_fn = function() {
|
|
4096
|
+
__privateSet(this, _cols, __privateGet(this, _cols).filter((c) => c.width !== void 0 || c.customWidth || c.hidden));
|
|
4097
|
+
};
|
|
2414
4098
|
getOrCreateRow_fn = function(rowIndex) {
|
|
2415
4099
|
let row = __privateGet(this, _rows).get(rowIndex);
|
|
2416
4100
|
if (!row) {
|
|
@@ -2419,9 +4103,64 @@ getOrCreateRow_fn = function(rowIndex) {
|
|
|
2419
4103
|
}
|
|
2420
4104
|
return row;
|
|
2421
4105
|
};
|
|
4106
|
+
var RID_PATTERN = /^rId(\d+)$/;
|
|
2422
4107
|
function escXml2(s) {
|
|
2423
4108
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2424
4109
|
}
|
|
4110
|
+
function escXmlAttr(s) {
|
|
4111
|
+
return escXml2(s).replace(/"/g, """);
|
|
4112
|
+
}
|
|
4113
|
+
function normalizeRef(ref) {
|
|
4114
|
+
const normalized = normalizeRefFast(ref);
|
|
4115
|
+
parseCellRef(normalized);
|
|
4116
|
+
return normalized;
|
|
4117
|
+
}
|
|
4118
|
+
function normalizeRangeRef(ref) {
|
|
4119
|
+
const normalized = normalizeRefFast(ref);
|
|
4120
|
+
const colon = normalized.indexOf(":");
|
|
4121
|
+
if (colon === -1) {
|
|
4122
|
+
parseCellRef(normalized);
|
|
4123
|
+
return normalized;
|
|
4124
|
+
}
|
|
4125
|
+
parseCellRef(normalized.slice(0, colon));
|
|
4126
|
+
parseCellRef(normalized.slice(colon + 1));
|
|
4127
|
+
return normalized;
|
|
4128
|
+
}
|
|
4129
|
+
function normalizeRefFast(ref) {
|
|
4130
|
+
return ref.indexOf("$") === -1 ? upperFast(ref) : ref.replace(/\$/g, "").toUpperCase();
|
|
4131
|
+
}
|
|
4132
|
+
function upperFast(s) {
|
|
4133
|
+
for (let i = 0; i < s.length; i++) {
|
|
4134
|
+
const code = s.charCodeAt(i);
|
|
4135
|
+
if (code >= 97 && code <= 122) return s.toUpperCase();
|
|
4136
|
+
}
|
|
4137
|
+
return s;
|
|
4138
|
+
}
|
|
4139
|
+
function refSplit(ref) {
|
|
4140
|
+
let i = 0;
|
|
4141
|
+
while (i < ref.length) {
|
|
4142
|
+
const code = ref.charCodeAt(i);
|
|
4143
|
+
if (code < 65 || code > 90) break;
|
|
4144
|
+
i++;
|
|
4145
|
+
}
|
|
4146
|
+
return i;
|
|
4147
|
+
}
|
|
4148
|
+
function columnFromRef(ref, split) {
|
|
4149
|
+
let col = 0;
|
|
4150
|
+
for (let i = 0; i < split; i++) col = col * 26 + (ref.charCodeAt(i) - 64);
|
|
4151
|
+
return col;
|
|
4152
|
+
}
|
|
4153
|
+
function translateRangeRef(ref, deltaRow, deltaCol) {
|
|
4154
|
+
const parts = ref.split(":").map((p) => {
|
|
4155
|
+
const { row, column } = parseCellRef(p);
|
|
4156
|
+
const r = row + deltaRow;
|
|
4157
|
+
const c = column + deltaCol;
|
|
4158
|
+
if (r < 1 || c < 1 || r > EXCEL_MAX_ROWS || c > EXCEL_MAX_COLUMNS) return null;
|
|
4159
|
+
return cellRefFromRowCol(r, c);
|
|
4160
|
+
});
|
|
4161
|
+
if (parts.some((p) => p === null)) return void 0;
|
|
4162
|
+
return parts.join(":");
|
|
4163
|
+
}
|
|
2425
4164
|
|
|
2426
4165
|
// src/model/defined-name.ts
|
|
2427
4166
|
var _names, _DefinedNameManager_instances, indexOf_fn;
|
|
@@ -2486,7 +4225,7 @@ indexOf_fn = function(name, scope) {
|
|
|
2486
4225
|
};
|
|
2487
4226
|
|
|
2488
4227
|
// src/model/workbook.ts
|
|
2489
|
-
var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _Workbook_instances, assertOpen_fn, buildZipEntries_fn;
|
|
4228
|
+
var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, structuralEdit_fn, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
|
|
2490
4229
|
var _Workbook = class _Workbook {
|
|
2491
4230
|
constructor(sharedStrings, styles) {
|
|
2492
4231
|
__privateAdd(this, _Workbook_instances);
|
|
@@ -2495,6 +4234,11 @@ var _Workbook = class _Workbook {
|
|
|
2495
4234
|
__privateAdd(this, _sheets, []);
|
|
2496
4235
|
__privateAdd(this, _definedNames, new DefinedNameManager());
|
|
2497
4236
|
__privateAdd(this, _closed, false);
|
|
4237
|
+
/** Parts carried over verbatim from the file this workbook was opened from. */
|
|
4238
|
+
__privateAdd(this, _preservedParts, /* @__PURE__ */ new Map());
|
|
4239
|
+
__privateAdd(this, _preservedContentTypes);
|
|
4240
|
+
/** Preserved targets whose referring worksheet has been removed. */
|
|
4241
|
+
__privateAdd(this, _orphanedTargets, []);
|
|
2498
4242
|
__privateSet(this, _sharedStrings3, sharedStrings);
|
|
2499
4243
|
__privateSet(this, _styles3, styles);
|
|
2500
4244
|
}
|
|
@@ -2535,8 +4279,24 @@ var _Workbook = class _Workbook {
|
|
|
2535
4279
|
const ws = new Worksheet(name, ss, sm);
|
|
2536
4280
|
if (state) ws.setVisibility(state);
|
|
2537
4281
|
ws.loadFromXml(wsXml);
|
|
4282
|
+
if (wsPath) {
|
|
4283
|
+
const dir = wsPath.slice(0, wsPath.lastIndexOf("/"));
|
|
4284
|
+
const file = wsPath.slice(wsPath.lastIndexOf("/") + 1);
|
|
4285
|
+
const wsRelsXml = readXmlEntry(entries, `${dir}/_rels/${file}.rels`);
|
|
4286
|
+
if (wsRelsXml) {
|
|
4287
|
+
ws.loadPreservedState(wsXml, parseRelationships(wsRelsXml, wsPath));
|
|
4288
|
+
const commentsPath = ws.commentsPartPath();
|
|
4289
|
+
if (commentsPath) {
|
|
4290
|
+
const commentsXml = readXmlEntry(entries, commentsPath);
|
|
4291
|
+
if (commentsXml) ws.loadCommentsXml(commentsXml);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
2538
4295
|
__privateGet(wb, _sheets).push({ worksheet: ws, sheetId, relId });
|
|
2539
4296
|
}
|
|
4297
|
+
__privateSet(wb, _preservedParts, collectPreservedParts(entries));
|
|
4298
|
+
const ctXml = readXmlEntry(entries, "[Content_Types].xml");
|
|
4299
|
+
if (ctXml) __privateSet(wb, _preservedContentTypes, parseContentTypes(ctXml));
|
|
2540
4300
|
for (const dn of getElementsByTagName(wbRoot, "definedName")) {
|
|
2541
4301
|
const name = getAttr(dn, "name");
|
|
2542
4302
|
if (!name) continue;
|
|
@@ -2586,6 +4346,30 @@ var _Workbook = class _Workbook {
|
|
|
2586
4346
|
__privateGet(this, _sheets).push({ worksheet: ws, sheetId, relId });
|
|
2587
4347
|
return ws;
|
|
2588
4348
|
}
|
|
4349
|
+
/**
|
|
4350
|
+
* Removes the worksheet with the given name, along with any defined names
|
|
4351
|
+
* scoped to it. Throws if the sheet does not exist or is the last remaining
|
|
4352
|
+
* one — a workbook must always keep at least one sheet.
|
|
4353
|
+
*
|
|
4354
|
+
* Sheet ids and relationship ids are re-derived from document order on save,
|
|
4355
|
+
* so the remaining sheets stay consistent after a removal.
|
|
4356
|
+
*/
|
|
4357
|
+
removeWorksheet(name) {
|
|
4358
|
+
__privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
|
|
4359
|
+
const idx = __privateGet(this, _sheets).findIndex((e) => e.worksheet.name === name);
|
|
4360
|
+
if (idx < 0) throw new Error(`Worksheet "${name}" not found.`);
|
|
4361
|
+
if (__privateGet(this, _sheets).length === 1) {
|
|
4362
|
+
throw new Error("Cannot remove the last worksheet; a workbook must have at least one.");
|
|
4363
|
+
}
|
|
4364
|
+
const removed = __privateGet(this, _sheets)[idx].worksheet;
|
|
4365
|
+
for (const rel of removed.preservedRelationships) {
|
|
4366
|
+
if (rel.resolved) __privateGet(this, _orphanedTargets).push(rel.resolved);
|
|
4367
|
+
}
|
|
4368
|
+
__privateGet(this, _sheets).splice(idx, 1);
|
|
4369
|
+
for (const dn of [...__privateGet(this, _definedNames).all]) {
|
|
4370
|
+
if (dn.scope === name) __privateGet(this, _definedNames).remove(dn.name, dn.scope);
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
2589
4373
|
/**
|
|
2590
4374
|
* Returns a worksheet by name. Throws if not found.
|
|
2591
4375
|
*/
|
|
@@ -2594,6 +4378,32 @@ var _Workbook = class _Workbook {
|
|
|
2594
4378
|
if (!entry) throw new Error(`Worksheet "${name}" not found.`);
|
|
2595
4379
|
return entry.worksheet;
|
|
2596
4380
|
}
|
|
4381
|
+
// ── Structural editing (workbook-wide) ───────────────────────────────────────
|
|
4382
|
+
/**
|
|
4383
|
+
* Inserts rows on one sheet and keeps the whole workbook consistent:
|
|
4384
|
+
* formulas on OTHER sheets that reference the edited sheet, and defined
|
|
4385
|
+
* names, are rewritten too. Prefer this over `Worksheet.insertRows` in a
|
|
4386
|
+
* multi-sheet workbook.
|
|
4387
|
+
*
|
|
4388
|
+
* @returns The edited sheet's result; `refErrors` additionally includes
|
|
4389
|
+
* sheet-qualified refs (e.g. `"Other!B2"`) for affected formula
|
|
4390
|
+
* cells on other sheets.
|
|
4391
|
+
*/
|
|
4392
|
+
insertRows(sheetName, at, count = 1) {
|
|
4393
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "insert", at, count);
|
|
4394
|
+
}
|
|
4395
|
+
/** Workbook-wide counterpart to `Worksheet.deleteRows`. See {@link insertRows}. */
|
|
4396
|
+
deleteRows(sheetName, at, count = 1) {
|
|
4397
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "delete", at, count);
|
|
4398
|
+
}
|
|
4399
|
+
/** Workbook-wide counterpart to `Worksheet.insertColumns`. See {@link insertRows}. */
|
|
4400
|
+
insertColumns(sheetName, at, count = 1) {
|
|
4401
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "insert", at, count);
|
|
4402
|
+
}
|
|
4403
|
+
/** Workbook-wide counterpart to `Worksheet.deleteColumns`. See {@link insertRows}. */
|
|
4404
|
+
deleteColumns(sheetName, at, count = 1) {
|
|
4405
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "delete", at, count);
|
|
4406
|
+
}
|
|
2597
4407
|
// ── Defined names (named ranges) ─────────────────────────────────────────────
|
|
2598
4408
|
/**
|
|
2599
4409
|
* Defines (or replaces) a named range.
|
|
@@ -2670,19 +4480,89 @@ _styles3 = new WeakMap();
|
|
|
2670
4480
|
_sheets = new WeakMap();
|
|
2671
4481
|
_definedNames = new WeakMap();
|
|
2672
4482
|
_closed = new WeakMap();
|
|
4483
|
+
_preservedParts = new WeakMap();
|
|
4484
|
+
_preservedContentTypes = new WeakMap();
|
|
4485
|
+
_orphanedTargets = new WeakMap();
|
|
2673
4486
|
_Workbook_instances = new WeakSet();
|
|
4487
|
+
structuralEdit_fn = function(sheetName, dim, kind, at, count) {
|
|
4488
|
+
__privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
|
|
4489
|
+
const target = this.getWorksheet(sheetName);
|
|
4490
|
+
const result = dim === "row" ? kind === "insert" ? target.insertRows(at, count) : target.deleteRows(at, count) : kind === "insert" ? target.insertColumns(at, count) : target.deleteColumns(at, count);
|
|
4491
|
+
const shift = { dim, kind, at, count, sheetName: target.name };
|
|
4492
|
+
const refErrors = [...result.refErrors];
|
|
4493
|
+
for (const ws of this.worksheets) {
|
|
4494
|
+
if (ws === target) continue;
|
|
4495
|
+
const external = ws.applyExternalShift(shift);
|
|
4496
|
+
for (const ref of external.refErrors) refErrors.push(`${ws.name}!${ref}`);
|
|
4497
|
+
}
|
|
4498
|
+
for (const dn of [...__privateGet(this, _definedNames).all]) {
|
|
4499
|
+
const rewritten = shiftFormulaRefs(dn.refersTo, shift, dn.scope ?? "");
|
|
4500
|
+
if (rewritten.changed) __privateGet(this, _definedNames).define(dn.name, rewritten.text, dn.scope);
|
|
4501
|
+
}
|
|
4502
|
+
return { refErrors };
|
|
4503
|
+
};
|
|
2674
4504
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
2675
4505
|
assertOpen_fn = function() {
|
|
2676
4506
|
if (__privateGet(this, _closed)) throw new Error("Workbook has been closed.");
|
|
2677
4507
|
};
|
|
4508
|
+
/**
|
|
4509
|
+
* Copies preserved source parts into the output, minus those that are now
|
|
4510
|
+
* orphaned. A part is dropped when the only relationship pointing at it has
|
|
4511
|
+
* gone (a drawing replaced by live providers, a sheet removed, an explicit
|
|
4512
|
+
* `clearDrawings()`) and nothing else still reaches it.
|
|
4513
|
+
*
|
|
4514
|
+
* @returns the set of paths written, so later steps can avoid colliding with
|
|
4515
|
+
* them when naming newly generated parts.
|
|
4516
|
+
*/
|
|
4517
|
+
seedPreservedParts_fn = function(entries) {
|
|
4518
|
+
if (__privateGet(this, _preservedParts).size === 0) return /* @__PURE__ */ new Set();
|
|
4519
|
+
const retained = /* @__PURE__ */ new Set();
|
|
4520
|
+
for (const { worksheet } of __privateGet(this, _sheets)) {
|
|
4521
|
+
for (const rel of worksheet.preservedRelationships) {
|
|
4522
|
+
if (rel.resolved) {
|
|
4523
|
+
for (const p of collectSubtree(__privateGet(this, _preservedParts), rel.resolved)) retained.add(p);
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
const candidates = [
|
|
4528
|
+
...__privateGet(this, _sheets).flatMap((e) => [...e.worksheet.droppedPreservedTargets]),
|
|
4529
|
+
...__privateGet(this, _orphanedTargets)
|
|
4530
|
+
];
|
|
4531
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
4532
|
+
for (const target of candidates) {
|
|
4533
|
+
for (const p of collectSubtree(__privateGet(this, _preservedParts), target)) {
|
|
4534
|
+
if (!retained.has(p)) dropped.add(p);
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
const written = /* @__PURE__ */ new Set();
|
|
4538
|
+
for (const [path, bytes] of __privateGet(this, _preservedParts)) {
|
|
4539
|
+
if (dropped.has(path)) continue;
|
|
4540
|
+
entries.set(path, bytes);
|
|
4541
|
+
written.add(path);
|
|
4542
|
+
}
|
|
4543
|
+
return written;
|
|
4544
|
+
};
|
|
4545
|
+
/**
|
|
4546
|
+
* Content-type declarations for preserved parts. `<Default>` entries are kept
|
|
4547
|
+
* wholesale — they are extension-scoped and are what types binary parts such
|
|
4548
|
+
* as images — while `<Override>` entries are filtered to parts that survived.
|
|
4549
|
+
*/
|
|
4550
|
+
preservedContentTypeDeclarations_fn = function(preservedPaths) {
|
|
4551
|
+
const ct = __privateGet(this, _preservedContentTypes);
|
|
4552
|
+
if (!ct) return { defaults: [], overrides: [] };
|
|
4553
|
+
const defaults = [...ct.defaults].filter(([ext]) => ext !== "rels" && ext !== "xml").map(([extension, contentType]) => ({ extension, contentType }));
|
|
4554
|
+
const overrides = [...ct.overrides].filter(([partName]) => preservedPaths.has(partName.replace(/^\//, ""))).map(([partName, contentType]) => ({ partName, contentType }));
|
|
4555
|
+
return { defaults, overrides };
|
|
4556
|
+
};
|
|
2678
4557
|
buildZipEntries_fn = function() {
|
|
2679
4558
|
const entries = /* @__PURE__ */ new Map();
|
|
2680
4559
|
const sheetCount = __privateGet(this, _sheets).length;
|
|
4560
|
+
const preservedPaths = __privateMethod(this, _Workbook_instances, seedPreservedParts_fn).call(this, entries);
|
|
2681
4561
|
entries.set("_rels/.rels", ROOT_RELS);
|
|
2682
|
-
const sheetMetas = __privateGet(this, _sheets).map((e) => ({
|
|
4562
|
+
const sheetMetas = __privateGet(this, _sheets).map((e, i) => ({
|
|
2683
4563
|
name: e.worksheet.name,
|
|
2684
|
-
sheetId:
|
|
2685
|
-
relationshipId:
|
|
4564
|
+
sheetId: i + 1,
|
|
4565
|
+
relationshipId: `rId${i + 1}`,
|
|
2686
4566
|
state: e.worksheet.visibility !== "visible" ? e.worksheet.visibility : void 0
|
|
2687
4567
|
}));
|
|
2688
4568
|
const definedNameMetas = __privateGet(this, _definedNames).all.map((dn) => {
|
|
@@ -2701,38 +4581,79 @@ buildZipEntries_fn = function() {
|
|
|
2701
4581
|
entries.set(`xl/worksheets/sheet${i + 1}.xml`, __privateGet(this, _sheets)[i].worksheet.buildXml());
|
|
2702
4582
|
}
|
|
2703
4583
|
const extraOverrides = [];
|
|
4584
|
+
const extraDefaults = [];
|
|
2704
4585
|
let drawingCounter = 0;
|
|
2705
4586
|
let partCounter = 0;
|
|
4587
|
+
let commentsCounter = 0;
|
|
2706
4588
|
for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
|
|
2707
4589
|
const ws = __privateGet(this, _sheets)[i].worksheet;
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
const drawingPath = `xl/drawings/drawing${drawingCounter}.xml`;
|
|
2711
|
-
const anchors = [];
|
|
2712
|
-
const drawingRels = [];
|
|
2713
|
-
ws.drawingProviders.forEach((provider, idx) => {
|
|
2714
|
-
const relId = `rId${idx + 1}`;
|
|
2715
|
-
anchors.push(provider.buildAnchorXml(relId));
|
|
2716
|
-
partCounter++;
|
|
2717
|
-
const part = provider.buildPart(partCounter);
|
|
2718
|
-
entries.set(part.path, part.content);
|
|
2719
|
-
extraOverrides.push({ partName: `/${part.path}`, contentType: part.contentType });
|
|
2720
|
-
const target = `../${part.path.slice("xl/".length)}`;
|
|
2721
|
-
drawingRels.push(
|
|
2722
|
-
` <Relationship Id="${relId}" Type="${part.relType}" Target="${target}"/>`
|
|
2723
|
-
);
|
|
2724
|
-
});
|
|
2725
|
-
entries.set(drawingPath, buildDrawingXml(anchors.join("")));
|
|
2726
|
-
entries.set(`xl/drawings/_rels/drawing${drawingCounter}.xml.rels`, buildRelsXml(drawingRels));
|
|
2727
|
-
extraOverrides.push({ partName: `/${drawingPath}`, contentType: CONTENT_TYPES.drawing });
|
|
2728
|
-
entries.set(
|
|
2729
|
-
`xl/worksheets/_rels/sheet${i + 1}.xml.rels`,
|
|
2730
|
-
buildRelsXml([
|
|
2731
|
-
` <Relationship Id="${WORKSHEET_DRAWING_REL_ID}" Type="${REL_TYPES.drawing}" Target="../drawings/drawing${drawingCounter}.xml"/>`
|
|
2732
|
-
])
|
|
4590
|
+
const sheetRels = ws.preservedRelationships.map(
|
|
4591
|
+
(r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${escapeXml(r.target)}"${r.external ? ' TargetMode="External"' : ""}/>`
|
|
2733
4592
|
);
|
|
4593
|
+
const generated = ws.generatedRels();
|
|
4594
|
+
for (const link of ws.hyperlinks) {
|
|
4595
|
+
const relId = generated.hyperlinks.get(link.ref);
|
|
4596
|
+
if (!relId || !link.target) continue;
|
|
4597
|
+
sheetRels.push(
|
|
4598
|
+
` <Relationship Id="${relId}" Type="${REL_TYPES.hyperlink}" Target="${escapeXml(link.target)}" TargetMode="External"/>`
|
|
4599
|
+
);
|
|
4600
|
+
}
|
|
4601
|
+
if (generated.comments && generated.vmlDrawing) {
|
|
4602
|
+
do {
|
|
4603
|
+
commentsCounter++;
|
|
4604
|
+
} while (preservedPaths.has(`xl/comments${commentsCounter}.xml`) || preservedPaths.has(`xl/drawings/vmlDrawing${commentsCounter}.vml`));
|
|
4605
|
+
const commentsPath = `xl/comments${commentsCounter}.xml`;
|
|
4606
|
+
const vmlPath = `xl/drawings/vmlDrawing${commentsCounter}.vml`;
|
|
4607
|
+
entries.set(commentsPath, ws.buildCommentsXml());
|
|
4608
|
+
entries.set(vmlPath, ws.buildCommentsVml());
|
|
4609
|
+
extraOverrides.push({ partName: `/${commentsPath}`, contentType: CONTENT_TYPES.comments });
|
|
4610
|
+
if (!extraDefaults.some((d) => d.extension === "vml")) {
|
|
4611
|
+
extraDefaults.push({ extension: "vml", contentType: CONTENT_TYPES.vmlDrawing });
|
|
4612
|
+
}
|
|
4613
|
+
sheetRels.push(
|
|
4614
|
+
` <Relationship Id="${generated.comments}" Type="${REL_TYPES.comments}" Target="../comments${commentsCounter}.xml"/>`,
|
|
4615
|
+
` <Relationship Id="${generated.vmlDrawing}" Type="${REL_TYPES.vmlDrawing}" Target="../drawings/vmlDrawing${commentsCounter}.vml"/>`
|
|
4616
|
+
);
|
|
4617
|
+
}
|
|
4618
|
+
if (ws.hasDrawings) {
|
|
4619
|
+
do {
|
|
4620
|
+
drawingCounter++;
|
|
4621
|
+
} while (preservedPaths.has(`xl/drawings/drawing${drawingCounter}.xml`));
|
|
4622
|
+
const drawingPath = `xl/drawings/drawing${drawingCounter}.xml`;
|
|
4623
|
+
const anchors = [];
|
|
4624
|
+
const drawingRels = [];
|
|
4625
|
+
ws.drawingProviders.forEach((provider, idx) => {
|
|
4626
|
+
const relId = `rId${idx + 1}`;
|
|
4627
|
+
anchors.push(provider.buildAnchorXml(relId));
|
|
4628
|
+
let part = provider.buildPart(++partCounter);
|
|
4629
|
+
while (preservedPaths.has(part.path)) part = provider.buildPart(++partCounter);
|
|
4630
|
+
entries.set(part.path, part.content);
|
|
4631
|
+
extraOverrides.push({ partName: `/${part.path}`, contentType: part.contentType });
|
|
4632
|
+
const target = `../${part.path.slice("xl/".length)}`;
|
|
4633
|
+
drawingRels.push(
|
|
4634
|
+
` <Relationship Id="${relId}" Type="${part.relType}" Target="${target}"/>`
|
|
4635
|
+
);
|
|
4636
|
+
});
|
|
4637
|
+
entries.set(drawingPath, buildDrawingXml(anchors.join("")));
|
|
4638
|
+
entries.set(`xl/drawings/_rels/drawing${drawingCounter}.xml.rels`, buildRelsXml(drawingRels));
|
|
4639
|
+
extraOverrides.push({ partName: `/${drawingPath}`, contentType: CONTENT_TYPES.drawing });
|
|
4640
|
+
sheetRels.push(
|
|
4641
|
+
` <Relationship Id="${ws.drawingRelId}" Type="${REL_TYPES.drawing}" Target="../drawings/drawing${drawingCounter}.xml"/>`
|
|
4642
|
+
);
|
|
4643
|
+
}
|
|
4644
|
+
if (sheetRels.length > 0) {
|
|
4645
|
+
entries.set(`xl/worksheets/_rels/sheet${i + 1}.xml.rels`, buildRelsXml(sheetRels));
|
|
4646
|
+
}
|
|
2734
4647
|
}
|
|
2735
|
-
|
|
4648
|
+
const preservedCt = __privateMethod(this, _Workbook_instances, preservedContentTypeDeclarations_fn).call(this, preservedPaths);
|
|
4649
|
+
entries.set(
|
|
4650
|
+
"[Content_Types].xml",
|
|
4651
|
+
buildContentTypes(
|
|
4652
|
+
sheetCount,
|
|
4653
|
+
[...preservedCt.overrides, ...extraOverrides],
|
|
4654
|
+
[...preservedCt.defaults, ...extraDefaults]
|
|
4655
|
+
)
|
|
4656
|
+
);
|
|
2736
4657
|
return entries;
|
|
2737
4658
|
};
|
|
2738
4659
|
var Workbook = _Workbook;
|
|
@@ -2755,6 +4676,8 @@ exports.CONTENT_TYPES = CONTENT_TYPES;
|
|
|
2755
4676
|
exports.Cell = Cell;
|
|
2756
4677
|
exports.CellStyle = CellStyle;
|
|
2757
4678
|
exports.DefinedNameManager = DefinedNameManager;
|
|
4679
|
+
exports.EXCEL_MAX_COLUMNS = EXCEL_MAX_COLUMNS;
|
|
4680
|
+
exports.EXCEL_MAX_ROWS = EXCEL_MAX_ROWS;
|
|
2758
4681
|
exports.ExcelAlignment = ExcelAlignment;
|
|
2759
4682
|
exports.ExcelBorder = ExcelBorder;
|
|
2760
4683
|
exports.ExcelBorderEdge = ExcelBorderEdge;
|
|
@@ -2783,8 +4706,12 @@ exports.cellRefFromRowCol = cellRefFromRowCol;
|
|
|
2783
4706
|
exports.columnLetter = columnLetter;
|
|
2784
4707
|
exports.columnNumber = columnNumber;
|
|
2785
4708
|
exports.decodeUtf8 = decodeUtf8;
|
|
4709
|
+
exports.decodeXmlEntities = decodeXmlEntities;
|
|
2786
4710
|
exports.encodeUtf8 = encodeUtf8;
|
|
2787
4711
|
exports.escapeXml = escapeXml;
|
|
4712
|
+
exports.formatDateCode = formatDateCode;
|
|
4713
|
+
exports.formatNumberWithCode = formatNumberWithCode;
|
|
4714
|
+
exports.formatRangeRef = formatRangeRef;
|
|
2788
4715
|
exports.fromOADate = fromOADate;
|
|
2789
4716
|
exports.getAttr = getAttr;
|
|
2790
4717
|
exports.getElementsByTagName = getElementsByTagName;
|
|
@@ -2798,6 +4725,11 @@ exports.readXmlEntry = readXmlEntry;
|
|
|
2798
4725
|
exports.requireXmlEntry = requireXmlEntry;
|
|
2799
4726
|
exports.setBinaryEntry = setBinaryEntry;
|
|
2800
4727
|
exports.setXmlEntry = setXmlEntry;
|
|
4728
|
+
exports.shiftFormulaRefs = shiftFormulaRefs;
|
|
4729
|
+
exports.shiftIndex = shiftIndex;
|
|
4730
|
+
exports.shiftRangeRef = shiftRangeRef;
|
|
4731
|
+
exports.shiftSpan = shiftSpan;
|
|
4732
|
+
exports.shiftSqref = shiftSqref;
|
|
2801
4733
|
exports.toOADate = toOADate;
|
|
2802
4734
|
exports.unzipXlsx = unzipXlsx;
|
|
2803
4735
|
exports.xml = xml;
|