@devmm/puredocs-excel 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2518 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +530 -11
- package/dist/index.d.ts +530 -11
- package/dist/index.js +2510 -236
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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);
|
|
@@ -192,6 +196,124 @@ function listEntries(entries, prefix) {
|
|
|
192
196
|
}
|
|
193
197
|
return results.sort();
|
|
194
198
|
}
|
|
199
|
+
|
|
200
|
+
// src/io/zip-container.ts
|
|
201
|
+
var LOCAL_HEADER = 67324752;
|
|
202
|
+
var CENTRAL_HEADER = 33639248;
|
|
203
|
+
var END_OF_CENTRAL_DIR = 101010256;
|
|
204
|
+
var DOS_DATE = 33;
|
|
205
|
+
var DOS_TIME = 0;
|
|
206
|
+
var MAX_ENTRIES = 65535;
|
|
207
|
+
var MAX_BYTES = 4294967295;
|
|
208
|
+
var CRC_TABLE = (() => {
|
|
209
|
+
const table = new Int32Array(256);
|
|
210
|
+
for (let i = 0; i < 256; i++) {
|
|
211
|
+
let c = i;
|
|
212
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
213
|
+
table[i] = c;
|
|
214
|
+
}
|
|
215
|
+
return table;
|
|
216
|
+
})();
|
|
217
|
+
function crc32(data) {
|
|
218
|
+
let c = -1;
|
|
219
|
+
for (let i = 0; i < data.length; i++) {
|
|
220
|
+
c = c >>> 8 ^ CRC_TABLE[(c ^ data[i]) & 255];
|
|
221
|
+
}
|
|
222
|
+
return (c ^ -1) >>> 0;
|
|
223
|
+
}
|
|
224
|
+
function exceedsZipLimits(parts) {
|
|
225
|
+
if (parts.length > MAX_ENTRIES) return true;
|
|
226
|
+
let total = 0;
|
|
227
|
+
for (const p of parts) {
|
|
228
|
+
total += 30 + utf8Length(p.name) * 2 + 46 + p.deflated.length;
|
|
229
|
+
if (total > MAX_BYTES || p.rawSize > MAX_BYTES) return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
function buildZipContainer(parts) {
|
|
234
|
+
const names = parts.map((p) => encodeName(p.name));
|
|
235
|
+
let localSize = 0;
|
|
236
|
+
let centralSize = 0;
|
|
237
|
+
for (let i = 0; i < parts.length; i++) {
|
|
238
|
+
localSize += 30 + names[i].bytes.length + parts[i].deflated.length;
|
|
239
|
+
centralSize += 46 + names[i].bytes.length;
|
|
240
|
+
}
|
|
241
|
+
const out = new Uint8Array(localSize + centralSize + 22);
|
|
242
|
+
const view = new DataView(out.buffer);
|
|
243
|
+
const offsets = [];
|
|
244
|
+
let at = 0;
|
|
245
|
+
for (let i = 0; i < parts.length; i++) {
|
|
246
|
+
const part = parts[i];
|
|
247
|
+
const name = names[i];
|
|
248
|
+
offsets.push(at);
|
|
249
|
+
view.setUint32(at, LOCAL_HEADER, true);
|
|
250
|
+
view.setUint16(at + 4, 20, true);
|
|
251
|
+
view.setUint16(at + 6, name.utf8Flag, true);
|
|
252
|
+
view.setUint16(at + 8, 8, true);
|
|
253
|
+
view.setUint16(at + 10, DOS_TIME, true);
|
|
254
|
+
view.setUint16(at + 12, DOS_DATE, true);
|
|
255
|
+
view.setUint32(at + 14, part.crc, true);
|
|
256
|
+
view.setUint32(at + 18, part.deflated.length, true);
|
|
257
|
+
view.setUint32(at + 22, part.rawSize, true);
|
|
258
|
+
view.setUint16(at + 26, name.bytes.length, true);
|
|
259
|
+
view.setUint16(at + 28, 0, true);
|
|
260
|
+
at += 30;
|
|
261
|
+
out.set(name.bytes, at);
|
|
262
|
+
at += name.bytes.length;
|
|
263
|
+
out.set(part.deflated, at);
|
|
264
|
+
at += part.deflated.length;
|
|
265
|
+
}
|
|
266
|
+
const centralStart = at;
|
|
267
|
+
for (let i = 0; i < parts.length; i++) {
|
|
268
|
+
const part = parts[i];
|
|
269
|
+
const name = names[i];
|
|
270
|
+
view.setUint32(at, CENTRAL_HEADER, true);
|
|
271
|
+
view.setUint16(at + 4, 20, true);
|
|
272
|
+
view.setUint16(at + 6, 20, true);
|
|
273
|
+
view.setUint16(at + 8, name.utf8Flag, true);
|
|
274
|
+
view.setUint16(at + 10, 8, true);
|
|
275
|
+
view.setUint16(at + 12, DOS_TIME, true);
|
|
276
|
+
view.setUint16(at + 14, DOS_DATE, true);
|
|
277
|
+
view.setUint32(at + 16, part.crc, true);
|
|
278
|
+
view.setUint32(at + 20, part.deflated.length, true);
|
|
279
|
+
view.setUint32(at + 24, part.rawSize, true);
|
|
280
|
+
view.setUint16(at + 28, name.bytes.length, true);
|
|
281
|
+
view.setUint16(at + 30, 0, true);
|
|
282
|
+
view.setUint16(at + 32, 0, true);
|
|
283
|
+
view.setUint16(at + 34, 0, true);
|
|
284
|
+
view.setUint16(at + 36, 0, true);
|
|
285
|
+
view.setUint32(at + 38, 0, true);
|
|
286
|
+
view.setUint32(at + 42, offsets[i], true);
|
|
287
|
+
at += 46;
|
|
288
|
+
out.set(name.bytes, at);
|
|
289
|
+
at += name.bytes.length;
|
|
290
|
+
}
|
|
291
|
+
view.setUint32(at, END_OF_CENTRAL_DIR, true);
|
|
292
|
+
view.setUint16(at + 4, 0, true);
|
|
293
|
+
view.setUint16(at + 6, 0, true);
|
|
294
|
+
view.setUint16(at + 8, parts.length, true);
|
|
295
|
+
view.setUint16(at + 10, parts.length, true);
|
|
296
|
+
view.setUint32(at + 12, at - centralStart, true);
|
|
297
|
+
view.setUint32(at + 16, centralStart, true);
|
|
298
|
+
view.setUint16(at + 20, 0, true);
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
function encodeName(name) {
|
|
302
|
+
const bytes = new TextEncoder().encode(name);
|
|
303
|
+
let ascii = true;
|
|
304
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
305
|
+
if (bytes[i] > 127) {
|
|
306
|
+
ascii = false;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return { bytes, utf8Flag: ascii ? 0 : 2048 };
|
|
311
|
+
}
|
|
312
|
+
function utf8Length(name) {
|
|
313
|
+
return new TextEncoder().encode(name).length;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/io/zip-writer.ts
|
|
195
317
|
function zipXlsx(entries) {
|
|
196
318
|
const zippable = {};
|
|
197
319
|
for (const [path, content] of entries) {
|
|
@@ -206,6 +328,95 @@ function zipXlsx(entries) {
|
|
|
206
328
|
);
|
|
207
329
|
}
|
|
208
330
|
}
|
|
331
|
+
var ASYNC_MIN_BYTES = 512 * 1024;
|
|
332
|
+
function createDeflateCache() {
|
|
333
|
+
return /* @__PURE__ */ new Map();
|
|
334
|
+
}
|
|
335
|
+
function compress(name, source, bytes) {
|
|
336
|
+
return {
|
|
337
|
+
name,
|
|
338
|
+
source,
|
|
339
|
+
deflated: fflate.deflateSync(bytes, { level: 6 }),
|
|
340
|
+
crc: crc32(bytes),
|
|
341
|
+
rawSize: bytes.length
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function zipXlsxCached(entries, cache) {
|
|
345
|
+
const parts = collectParts(entries, cache);
|
|
346
|
+
pruneCache(entries, cache);
|
|
347
|
+
return exceedsZipLimits(parts) ? zipXlsx(entries) : buildZipContainer(parts);
|
|
348
|
+
}
|
|
349
|
+
async function zipXlsxCachedAsync(entries, cache) {
|
|
350
|
+
const order = [];
|
|
351
|
+
const ready = /* @__PURE__ */ new Map();
|
|
352
|
+
const jobs = [];
|
|
353
|
+
const todo = [];
|
|
354
|
+
let todoBytes = 0;
|
|
355
|
+
for (const [path, content] of entries) {
|
|
356
|
+
order.push(path);
|
|
357
|
+
const cached = cache.get(path);
|
|
358
|
+
if (cached && cached.source === content) {
|
|
359
|
+
ready.set(path, cached);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const bytes = typeof content === "string" ? encodeUtf8(content) : content;
|
|
363
|
+
todo.push({ path, content, bytes });
|
|
364
|
+
todoBytes += bytes.length;
|
|
365
|
+
}
|
|
366
|
+
if (todoBytes <= ASYNC_MIN_BYTES) {
|
|
367
|
+
for (const { path, content, bytes } of todo) {
|
|
368
|
+
const compressed = compress(path, content, bytes);
|
|
369
|
+
cache.set(path, compressed);
|
|
370
|
+
ready.set(path, compressed);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
for (const { path, content, bytes } of todo) {
|
|
374
|
+
jobs.push(deflateAsync(bytes).then((deflated) => ({
|
|
375
|
+
name: path,
|
|
376
|
+
source: content,
|
|
377
|
+
deflated,
|
|
378
|
+
crc: crc32(bytes),
|
|
379
|
+
rawSize: bytes.length
|
|
380
|
+
})));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for (const part of await Promise.all(jobs)) {
|
|
384
|
+
cache.set(part.name, part);
|
|
385
|
+
ready.set(part.name, part);
|
|
386
|
+
}
|
|
387
|
+
pruneCache(entries, cache);
|
|
388
|
+
const parts = order.map((path) => ready.get(path));
|
|
389
|
+
return exceedsZipLimits(parts) ? zipXlsx(entries) : buildZipContainer(parts);
|
|
390
|
+
}
|
|
391
|
+
function deflateAsync(bytes) {
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
fflate.deflate(bytes, { level: 6 }, (err, data) => {
|
|
394
|
+
if (err) reject(new Error(`Failed to create xlsx: ${err.message}`));
|
|
395
|
+
else resolve(data);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function collectParts(entries, cache) {
|
|
400
|
+
const parts = [];
|
|
401
|
+
for (const [path, content] of entries) {
|
|
402
|
+
const cached = cache.get(path);
|
|
403
|
+
if (cached && cached.source === content) {
|
|
404
|
+
parts.push(cached);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const bytes = typeof content === "string" ? encodeUtf8(content) : content;
|
|
408
|
+
const part = compress(path, content, bytes);
|
|
409
|
+
cache.set(path, part);
|
|
410
|
+
parts.push(part);
|
|
411
|
+
}
|
|
412
|
+
return parts;
|
|
413
|
+
}
|
|
414
|
+
function pruneCache(entries, cache) {
|
|
415
|
+
if (cache.size === entries.size) return;
|
|
416
|
+
for (const path of [...cache.keys()]) {
|
|
417
|
+
if (!entries.has(path)) cache.delete(path);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
209
420
|
function setXmlEntry(entries, path, xml2) {
|
|
210
421
|
entries.set(path, xml2);
|
|
211
422
|
}
|
|
@@ -296,7 +507,10 @@ var REL_TYPES = {
|
|
|
296
507
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
297
508
|
chart: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
|
|
298
509
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
299
|
-
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
|
510
|
+
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
511
|
+
comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
|
|
512
|
+
vmlDrawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing",
|
|
513
|
+
calcChain: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"
|
|
300
514
|
};
|
|
301
515
|
var CONTENT_TYPES = {
|
|
302
516
|
workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
@@ -306,7 +520,10 @@ var CONTENT_TYPES = {
|
|
|
306
520
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
307
521
|
chart: "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
|
|
308
522
|
relationships: "application/vnd.openxmlformats-package.relationships+xml",
|
|
309
|
-
coreProperties: "application/vnd.openxmlformats-package.core-properties+xml"
|
|
523
|
+
coreProperties: "application/vnd.openxmlformats-package.core-properties+xml",
|
|
524
|
+
comments: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
|
|
525
|
+
vmlDrawing: "application/vnd.openxmlformats-officedocument.vmlDrawing",
|
|
526
|
+
calcChain: "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"
|
|
310
527
|
};
|
|
311
528
|
|
|
312
529
|
// src/io/ooxml-templates.ts
|
|
@@ -339,18 +556,20 @@ var ROOT_RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
339
556
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
340
557
|
<Relationship Id="rId1" Type="${REL_TYPES.officeDocument}" Target="xl/workbook.xml"/>
|
|
341
558
|
</Relationships>`;
|
|
342
|
-
function buildWorkbookRels(sheetCount) {
|
|
559
|
+
function buildWorkbookRels(sheetCount, includeCalcChain = false) {
|
|
343
560
|
const sheetRels = Array.from(
|
|
344
561
|
{ length: sheetCount },
|
|
345
562
|
(_, i) => ` <Relationship Id="rId${i + 1}" Type="${REL_TYPES.worksheet}" Target="worksheets/sheet${i + 1}.xml"/>`
|
|
346
563
|
).join("\n");
|
|
347
564
|
const stylesId = sheetCount + 1;
|
|
348
565
|
const sharedId = sheetCount + 2;
|
|
566
|
+
const calcChainRel = includeCalcChain ? `
|
|
567
|
+
<Relationship Id="rId${sheetCount + 3}" Type="${REL_TYPES.calcChain}" Target="calcChain.xml"/>` : "";
|
|
349
568
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
350
569
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
351
570
|
${sheetRels}
|
|
352
571
|
<Relationship Id="rId${stylesId}" Type="${REL_TYPES.styles}" Target="styles.xml"/>
|
|
353
|
-
<Relationship Id="rId${sharedId}" Type="${REL_TYPES.sharedStrings}" Target="sharedStrings.xml"
|
|
572
|
+
<Relationship Id="rId${sharedId}" Type="${REL_TYPES.sharedStrings}" Target="sharedStrings.xml"/>${calcChainRel}
|
|
354
573
|
</Relationships>`;
|
|
355
574
|
}
|
|
356
575
|
function buildWorkbookXml(sheets, definedNames = []) {
|
|
@@ -451,8 +670,9 @@ function parseRelationships(relsXml, ownerPath) {
|
|
|
451
670
|
const tag = m[0];
|
|
452
671
|
const id = /\bId="([^"]*)"/.exec(tag)?.[1];
|
|
453
672
|
const type = /\bType="([^"]*)"/.exec(tag)?.[1];
|
|
454
|
-
const
|
|
455
|
-
if (!id || !type ||
|
|
673
|
+
const rawTarget = /\bTarget="([^"]*)"/.exec(tag)?.[1];
|
|
674
|
+
if (!id || !type || rawTarget === void 0) continue;
|
|
675
|
+
const target = decodeXmlEntities(rawTarget);
|
|
456
676
|
const external = /TargetMode="External"/.test(tag);
|
|
457
677
|
rels.push({
|
|
458
678
|
id,
|
|
@@ -954,8 +1174,12 @@ var ExcelBorderEdge = class _ExcelBorderEdge {
|
|
|
954
1174
|
cacheKey() {
|
|
955
1175
|
return `${this.style}:${this.color?.toString() ?? "null"}`;
|
|
956
1176
|
}
|
|
1177
|
+
/** An independent copy. ExcelColor is immutable, so it is shared. */
|
|
1178
|
+
clone() {
|
|
1179
|
+
return new _ExcelBorderEdge(this.style, this.color);
|
|
1180
|
+
}
|
|
957
1181
|
};
|
|
958
|
-
var
|
|
1182
|
+
var ExcelBorder = class _ExcelBorder {
|
|
959
1183
|
constructor() {
|
|
960
1184
|
this.left = new ExcelBorderEdge();
|
|
961
1185
|
this.right = new ExcelBorderEdge();
|
|
@@ -995,6 +1219,33 @@ var _ExcelBorder = class _ExcelBorder {
|
|
|
995
1219
|
b.right = new ExcelBorderEdge(style, color);
|
|
996
1220
|
return b;
|
|
997
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* A border with no edges set.
|
|
1224
|
+
*
|
|
1225
|
+
* A fresh instance per access, not a shared singleton. `ExcelBorder` is mutable
|
|
1226
|
+
* (`border.left.style = …` is the normal way to use it), so a shared instance
|
|
1227
|
+
* handed to two styles let an edit through one of them reach the other — and,
|
|
1228
|
+
* once anything had assigned `none` and then modified it, every later
|
|
1229
|
+
* `ExcelBorder.none` came back already carrying that edge.
|
|
1230
|
+
*/
|
|
1231
|
+
static get none() {
|
|
1232
|
+
return new _ExcelBorder();
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* A deep copy: the edges are new objects, so mutating the copy cannot reach the
|
|
1236
|
+
* original. {@link CellStyle.clone} relies on this.
|
|
1237
|
+
*/
|
|
1238
|
+
clone() {
|
|
1239
|
+
const b = new _ExcelBorder();
|
|
1240
|
+
b.left = this.left.clone();
|
|
1241
|
+
b.right = this.right.clone();
|
|
1242
|
+
b.top = this.top.clone();
|
|
1243
|
+
b.bottom = this.bottom.clone();
|
|
1244
|
+
b.diagonal = this.diagonal.clone();
|
|
1245
|
+
b.diagonalDown = this.diagonalDown;
|
|
1246
|
+
b.diagonalUp = this.diagonalUp;
|
|
1247
|
+
return b;
|
|
1248
|
+
}
|
|
998
1249
|
// ── Serialisation ─────────────────────────────────────────────────────────
|
|
999
1250
|
toXml() {
|
|
1000
1251
|
const dd = this.diagonalDown ? ' diagonalDown="1"' : "";
|
|
@@ -1040,8 +1291,6 @@ var _ExcelBorder = class _ExcelBorder {
|
|
|
1040
1291
|
].join("|");
|
|
1041
1292
|
}
|
|
1042
1293
|
};
|
|
1043
|
-
_ExcelBorder.none = new _ExcelBorder();
|
|
1044
|
-
var ExcelBorder = _ExcelBorder;
|
|
1045
1294
|
|
|
1046
1295
|
// src/style/excel-alignment.ts
|
|
1047
1296
|
var ExcelHorizontalAlignment = /* @__PURE__ */ ((ExcelHorizontalAlignment3) => {
|
|
@@ -1334,7 +1583,7 @@ var _CellStyle = class _CellStyle {
|
|
|
1334
1583
|
const s = new _CellStyle();
|
|
1335
1584
|
if (this.font) s.font = this.font.clone();
|
|
1336
1585
|
s.fill = this.fill;
|
|
1337
|
-
s.border = this.border;
|
|
1586
|
+
s.border = this.border?.clone();
|
|
1338
1587
|
s.alignment = this.alignment ? Object.assign(new ExcelAlignment(), this.alignment) : void 0;
|
|
1339
1588
|
s.numberFormat = this.numberFormat;
|
|
1340
1589
|
s.isLocked = this.isLocked;
|
|
@@ -1357,8 +1606,46 @@ ensureAlignment_fn = function() {
|
|
|
1357
1606
|
};
|
|
1358
1607
|
var CellStyle = _CellStyle;
|
|
1359
1608
|
|
|
1609
|
+
// src/model/oa-date.ts
|
|
1610
|
+
var OA_EPOCH_MS = Date.UTC(1899, 11, 30, 0, 0, 0, 0);
|
|
1611
|
+
var MS_PER_DAY = 864e5;
|
|
1612
|
+
function toOADate(date) {
|
|
1613
|
+
const utcMs = Date.UTC(
|
|
1614
|
+
date.getFullYear(),
|
|
1615
|
+
date.getMonth(),
|
|
1616
|
+
date.getDate(),
|
|
1617
|
+
date.getHours(),
|
|
1618
|
+
date.getMinutes(),
|
|
1619
|
+
date.getSeconds(),
|
|
1620
|
+
date.getMilliseconds()
|
|
1621
|
+
);
|
|
1622
|
+
return (utcMs - OA_EPOCH_MS) / MS_PER_DAY;
|
|
1623
|
+
}
|
|
1624
|
+
function fromOADate(oaDate) {
|
|
1625
|
+
const ms = OA_EPOCH_MS + oaDate * MS_PER_DAY;
|
|
1626
|
+
const d = new Date(ms);
|
|
1627
|
+
return new Date(
|
|
1628
|
+
d.getUTCFullYear(),
|
|
1629
|
+
d.getUTCMonth(),
|
|
1630
|
+
d.getUTCDate(),
|
|
1631
|
+
d.getUTCHours(),
|
|
1632
|
+
d.getUTCMinutes(),
|
|
1633
|
+
d.getUTCSeconds(),
|
|
1634
|
+
d.getUTCMilliseconds()
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
var DATE_FORMAT_IDS = /* @__PURE__ */ new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]);
|
|
1638
|
+
function isDateFormatId(formatId) {
|
|
1639
|
+
return DATE_FORMAT_IDS.has(formatId);
|
|
1640
|
+
}
|
|
1641
|
+
function isDateFormatCode(formatCode) {
|
|
1642
|
+
const lower = formatCode.toLowerCase();
|
|
1643
|
+
return lower.includes("y") || lower.includes("d") || lower.includes("m") && !lower.includes("mm:") || // m = month unless part of mm:ss
|
|
1644
|
+
lower.includes("h") || lower.includes("am") || lower.includes("pm");
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1360
1647
|
// src/style/style-manager.ts
|
|
1361
|
-
var _StyleManager_instances, initDefaults_fn, getOrCreateFont_fn, getOrCreateFill_fn, getOrCreateBorder_fn, getOrCreateNumFmt_fn, cellFormatKey_fn, cfToXml_fn;
|
|
1648
|
+
var _numFmtMapCache, _dateStyleMemo, _StyleManager_instances, numFmtMap_fn, invalidateFormatCaches_fn, initDefaults_fn, getOrCreateFont_fn, getOrCreateFill_fn, getOrCreateBorder_fn, getOrCreateNumFmt_fn, cellFormatKey_fn, cfToXml_fn;
|
|
1362
1649
|
var StyleManager = class {
|
|
1363
1650
|
constructor() {
|
|
1364
1651
|
__privateAdd(this, _StyleManager_instances);
|
|
@@ -1377,6 +1664,21 @@ var StyleManager = class {
|
|
|
1377
1664
|
this.cellXfCache = /* @__PURE__ */ new Map();
|
|
1378
1665
|
this.nextCustomNumFmtId = 164;
|
|
1379
1666
|
this.isDirty = false;
|
|
1667
|
+
/**
|
|
1668
|
+
* `numFmtId` → format code for the custom (≥164) formats, materialised once.
|
|
1669
|
+
* Rebuilding it per lookup made every numeric cell read O(custom formats),
|
|
1670
|
+
* and reading a cell's value has to consult it to tell dates from numbers.
|
|
1671
|
+
* Invalidated whenever `numFmts` changes — see {@link #invalidateFormatCaches}.
|
|
1672
|
+
*/
|
|
1673
|
+
__privateAdd(this, _numFmtMapCache);
|
|
1674
|
+
/**
|
|
1675
|
+
* cellXf index → does its number format render a date? Memoised because
|
|
1676
|
+
* {@link Cell.getValue} asks this for every numeric cell, and answering it
|
|
1677
|
+
* from scratch reconstructs a whole CellStyle. Entries stay valid because
|
|
1678
|
+
* `cellXfs` and `numFmts` are append-only: an existing index never changes
|
|
1679
|
+
* meaning.
|
|
1680
|
+
*/
|
|
1681
|
+
__privateAdd(this, _dateStyleMemo, /* @__PURE__ */ new Map());
|
|
1380
1682
|
__privateMethod(this, _StyleManager_instances, initDefaults_fn).call(this);
|
|
1381
1683
|
}
|
|
1382
1684
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -1428,10 +1730,7 @@ var StyleManager = class {
|
|
|
1428
1730
|
const borderEntry = this.borders[cf.borderId];
|
|
1429
1731
|
if (borderEntry && cf.applyBorder) s.border = borderEntry.border;
|
|
1430
1732
|
if (cf.applyNumFmt) {
|
|
1431
|
-
|
|
1432
|
-
this.numFmts.map((n) => [n.id, n.code])
|
|
1433
|
-
);
|
|
1434
|
-
s.numberFormat = ExcelNumberFormat.fromFormatId(cf.numFmtId, customMap);
|
|
1733
|
+
s.numberFormat = ExcelNumberFormat.fromFormatId(cf.numFmtId, __privateMethod(this, _StyleManager_instances, numFmtMap_fn).call(this));
|
|
1435
1734
|
}
|
|
1436
1735
|
if (cf.alignment && cf.applyAlign) s.alignment = cf.alignment;
|
|
1437
1736
|
if (cf.applyProt) {
|
|
@@ -1440,6 +1739,29 @@ var StyleManager = class {
|
|
|
1440
1739
|
}
|
|
1441
1740
|
return s;
|
|
1442
1741
|
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Whether the cellXf at `styleIndex` formats its value as a date. This is the
|
|
1744
|
+
* one style question the value-reading path has to ask, so it is answered
|
|
1745
|
+
* without reconstructing a CellStyle: the result is derived from the stored
|
|
1746
|
+
* format id/code and memoised per index.
|
|
1747
|
+
*
|
|
1748
|
+
* Equivalent to `getCellStyle(i).numberFormat` being date-like, by
|
|
1749
|
+
* construction — both read the same `applyNumFmt`/`numFmtId` pair through
|
|
1750
|
+
* {@link ExcelNumberFormat.fromFormatId}.
|
|
1751
|
+
*/
|
|
1752
|
+
isDateStyle(styleIndex) {
|
|
1753
|
+
const memo = __privateGet(this, _dateStyleMemo).get(styleIndex);
|
|
1754
|
+
if (memo !== void 0) return memo;
|
|
1755
|
+
const cf = this.cellXfs[styleIndex];
|
|
1756
|
+
if (!cf) return false;
|
|
1757
|
+
let isDate = false;
|
|
1758
|
+
if (cf.applyNumFmt) {
|
|
1759
|
+
const fmt = ExcelNumberFormat.fromFormatId(cf.numFmtId, __privateMethod(this, _StyleManager_instances, numFmtMap_fn).call(this));
|
|
1760
|
+
isDate = isDateFormatId(fmt.formatId) || isDateFormatCode(fmt.formatCode);
|
|
1761
|
+
}
|
|
1762
|
+
__privateGet(this, _dateStyleMemo).set(styleIndex, isDate);
|
|
1763
|
+
return isDate;
|
|
1764
|
+
}
|
|
1443
1765
|
// ── XML serialisation ──────────────────────────────────────────────────────
|
|
1444
1766
|
/** Builds the complete xl/styles.xml string. */
|
|
1445
1767
|
buildStylesXml() {
|
|
@@ -1469,6 +1791,7 @@ ${cellXfsXml}
|
|
|
1469
1791
|
this.numFmtCache.clear();
|
|
1470
1792
|
this.cellXfCache.clear();
|
|
1471
1793
|
this.nextCustomNumFmtId = 164;
|
|
1794
|
+
__privateMethod(this, _StyleManager_instances, invalidateFormatCaches_fn).call(this);
|
|
1472
1795
|
const root = parseXml(stylesXml);
|
|
1473
1796
|
for (const el of getElementsByTagName(root, "numFmt")) {
|
|
1474
1797
|
const id = parseInt(getAttr(el, "numFmtId") ?? "0", 10);
|
|
@@ -1527,9 +1850,23 @@ ${cellXfsXml}
|
|
|
1527
1850
|
}
|
|
1528
1851
|
if (this.fonts.length === 0) __privateMethod(this, _StyleManager_instances, initDefaults_fn).call(this);
|
|
1529
1852
|
if (this.cellXfs.length === 0) __privateMethod(this, _StyleManager_instances, initDefaults_fn).call(this);
|
|
1853
|
+
__privateMethod(this, _StyleManager_instances, invalidateFormatCaches_fn).call(this);
|
|
1530
1854
|
}
|
|
1531
1855
|
};
|
|
1856
|
+
_numFmtMapCache = new WeakMap();
|
|
1857
|
+
_dateStyleMemo = new WeakMap();
|
|
1532
1858
|
_StyleManager_instances = new WeakSet();
|
|
1859
|
+
numFmtMap_fn = function() {
|
|
1860
|
+
if (!__privateGet(this, _numFmtMapCache)) {
|
|
1861
|
+
__privateSet(this, _numFmtMapCache, new Map(this.numFmts.map((n) => [n.id, n.code])));
|
|
1862
|
+
}
|
|
1863
|
+
return __privateGet(this, _numFmtMapCache);
|
|
1864
|
+
};
|
|
1865
|
+
/** Drops the derived format caches after `numFmts` or `cellXfs` changed. */
|
|
1866
|
+
invalidateFormatCaches_fn = function() {
|
|
1867
|
+
__privateSet(this, _numFmtMapCache, void 0);
|
|
1868
|
+
__privateGet(this, _dateStyleMemo).clear();
|
|
1869
|
+
};
|
|
1533
1870
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
1534
1871
|
initDefaults_fn = function() {
|
|
1535
1872
|
const defaultFont = new ExcelFont({ name: "Calibri", size: 11 });
|
|
@@ -1602,6 +1939,7 @@ getOrCreateNumFmt_fn = function(fmt) {
|
|
|
1602
1939
|
const id = this.nextCustomNumFmtId++;
|
|
1603
1940
|
this.numFmts.push({ id, code: fmt.formatCode });
|
|
1604
1941
|
this.numFmtCache.set(fmt.formatCode, id);
|
|
1942
|
+
__privateMethod(this, _StyleManager_instances, invalidateFormatCaches_fn).call(this);
|
|
1605
1943
|
this.isDirty = true;
|
|
1606
1944
|
return id;
|
|
1607
1945
|
};
|
|
@@ -1671,10 +2009,15 @@ function splitSections(code) {
|
|
|
1671
2009
|
sections.push(current);
|
|
1672
2010
|
return sections;
|
|
1673
2011
|
}
|
|
2012
|
+
function hasDateTokens(section) {
|
|
2013
|
+
const bare = section.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "");
|
|
2014
|
+
return /[ymdhsYMDHS]/.test(bare);
|
|
2015
|
+
}
|
|
1674
2016
|
function isUnsupported(section) {
|
|
1675
2017
|
if (/\[[<>=]/.test(section)) return true;
|
|
1676
2018
|
if (/[?]/.test(section)) return true;
|
|
1677
2019
|
if (/[Ee][+-]/.test(section)) return true;
|
|
2020
|
+
if (hasDateTokens(section)) return true;
|
|
1678
2021
|
return false;
|
|
1679
2022
|
}
|
|
1680
2023
|
function parseSection(section) {
|
|
@@ -1768,12 +2111,177 @@ function formatNumberWithCode(value, formatCode) {
|
|
|
1768
2111
|
return renderNumber(magnitude, parseSection(section));
|
|
1769
2112
|
}
|
|
1770
2113
|
|
|
2114
|
+
// src/style/date-format-renderer.ts
|
|
2115
|
+
var MONTHS = [
|
|
2116
|
+
"January",
|
|
2117
|
+
"February",
|
|
2118
|
+
"March",
|
|
2119
|
+
"April",
|
|
2120
|
+
"May",
|
|
2121
|
+
"June",
|
|
2122
|
+
"July",
|
|
2123
|
+
"August",
|
|
2124
|
+
"September",
|
|
2125
|
+
"October",
|
|
2126
|
+
"November",
|
|
2127
|
+
"December"
|
|
2128
|
+
];
|
|
2129
|
+
var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
2130
|
+
function firstSection(code) {
|
|
2131
|
+
let inQuote = false;
|
|
2132
|
+
for (let i = 0; i < code.length; i++) {
|
|
2133
|
+
const ch = code[i];
|
|
2134
|
+
if (ch === '"') inQuote = !inQuote;
|
|
2135
|
+
else if (ch === ";" && !inQuote) return code.slice(0, i);
|
|
2136
|
+
}
|
|
2137
|
+
return code;
|
|
2138
|
+
}
|
|
2139
|
+
function tokenize(section) {
|
|
2140
|
+
const tokens = [];
|
|
2141
|
+
let i = 0;
|
|
2142
|
+
const n = section.length;
|
|
2143
|
+
while (i < n) {
|
|
2144
|
+
const ch = section[i];
|
|
2145
|
+
if (ch === '"') {
|
|
2146
|
+
let j = i + 1;
|
|
2147
|
+
let text = "";
|
|
2148
|
+
while (j < n && section[j] !== '"') text += section[j++];
|
|
2149
|
+
tokens.push({ kind: "literal", text });
|
|
2150
|
+
i = j + 1;
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
if (ch === "\\") {
|
|
2154
|
+
tokens.push({ kind: "literal", text: section[i + 1] ?? "" });
|
|
2155
|
+
i += 2;
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
if (ch === "[") {
|
|
2159
|
+
const close = section.indexOf("]", i);
|
|
2160
|
+
if (close === -1) return null;
|
|
2161
|
+
const inner = section.slice(i + 1, close);
|
|
2162
|
+
if (/^[hmsHMS]+$/.test(inner)) return null;
|
|
2163
|
+
i = close + 1;
|
|
2164
|
+
continue;
|
|
2165
|
+
}
|
|
2166
|
+
const rest = section.slice(i);
|
|
2167
|
+
const ampm = /^(AM\/PM|A\/P|am\/pm|a\/p)/.exec(rest);
|
|
2168
|
+
if (ampm) {
|
|
2169
|
+
tokens.push({ kind: "ampm", text: ampm[1] });
|
|
2170
|
+
i += ampm[1].length;
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
const lower = ch.toLowerCase();
|
|
2174
|
+
if ("ymdhs".includes(lower)) {
|
|
2175
|
+
let j = i;
|
|
2176
|
+
while (j < n && section[j].toLowerCase() === lower) j++;
|
|
2177
|
+
tokens.push({ kind: lower, text: section.slice(i, j) });
|
|
2178
|
+
i = j;
|
|
2179
|
+
if (lower === "s" && section[i] === "." && /\d|0/.test(section[i + 1] ?? "")) return null;
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (/[a-zA-Z]/.test(ch)) return null;
|
|
2183
|
+
tokens.push({ kind: "literal", text: ch });
|
|
2184
|
+
i++;
|
|
2185
|
+
}
|
|
2186
|
+
return tokens;
|
|
2187
|
+
}
|
|
2188
|
+
function resolveMonthVsMinute(tokens) {
|
|
2189
|
+
const dateKinds = /* @__PURE__ */ new Set(["y", "m", "d", "h", "s"]);
|
|
2190
|
+
for (let t = 0; t < tokens.length; t++) {
|
|
2191
|
+
const token = tokens[t];
|
|
2192
|
+
if (token.kind !== "m" || token.text.length > 2) continue;
|
|
2193
|
+
let prev;
|
|
2194
|
+
for (let p = t - 1; p >= 0; p--) {
|
|
2195
|
+
if (dateKinds.has(tokens[p].kind)) {
|
|
2196
|
+
prev = tokens[p];
|
|
2197
|
+
break;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
let next;
|
|
2201
|
+
for (let q = t + 1; q < tokens.length; q++) {
|
|
2202
|
+
if (dateKinds.has(tokens[q].kind)) {
|
|
2203
|
+
next = tokens[q];
|
|
2204
|
+
break;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (prev?.kind === "h" || next?.kind === "s") token.kind = "minute";
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
var pad = (v, len) => String(v).padStart(len, "0");
|
|
2211
|
+
function formatDateCode(date, formatCode) {
|
|
2212
|
+
if (!formatCode || formatCode === "General") return null;
|
|
2213
|
+
const tokens = tokenize(firstSection(formatCode));
|
|
2214
|
+
if (!tokens) return null;
|
|
2215
|
+
if (!tokens.some((t) => "ymdhs".includes(t.kind))) return null;
|
|
2216
|
+
resolveMonthVsMinute(tokens);
|
|
2217
|
+
const twelveHour = tokens.some((t) => t.kind === "ampm");
|
|
2218
|
+
const hours24 = date.getHours();
|
|
2219
|
+
const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
|
|
2220
|
+
let out = "";
|
|
2221
|
+
for (const t of tokens) {
|
|
2222
|
+
switch (t.kind) {
|
|
2223
|
+
case "literal":
|
|
2224
|
+
out += t.text;
|
|
2225
|
+
break;
|
|
2226
|
+
case "y":
|
|
2227
|
+
out += t.text.length <= 2 ? pad(date.getFullYear() % 100, 2) : String(date.getFullYear());
|
|
2228
|
+
break;
|
|
2229
|
+
case "m": {
|
|
2230
|
+
const month = date.getMonth();
|
|
2231
|
+
if (t.text.length === 1) out += String(month + 1);
|
|
2232
|
+
else if (t.text.length === 2) out += pad(month + 1, 2);
|
|
2233
|
+
else if (t.text.length === 3) out += MONTHS[month].slice(0, 3);
|
|
2234
|
+
else if (t.text.length === 5) out += MONTHS[month][0];
|
|
2235
|
+
else out += MONTHS[month];
|
|
2236
|
+
break;
|
|
2237
|
+
}
|
|
2238
|
+
case "minute":
|
|
2239
|
+
out += t.text.length === 1 ? String(date.getMinutes()) : pad(date.getMinutes(), 2);
|
|
2240
|
+
break;
|
|
2241
|
+
case "d": {
|
|
2242
|
+
const day = date.getDate();
|
|
2243
|
+
if (t.text.length === 1) out += String(day);
|
|
2244
|
+
else if (t.text.length === 2) out += pad(day, 2);
|
|
2245
|
+
else if (t.text.length === 3) out += DAYS[date.getDay()].slice(0, 3);
|
|
2246
|
+
else out += DAYS[date.getDay()];
|
|
2247
|
+
break;
|
|
2248
|
+
}
|
|
2249
|
+
case "h": {
|
|
2250
|
+
const h = twelveHour ? hours12 : hours24;
|
|
2251
|
+
out += t.text.length === 1 ? String(h) : pad(h, 2);
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
case "s":
|
|
2255
|
+
out += t.text.length === 1 ? String(date.getSeconds()) : pad(date.getSeconds(), 2);
|
|
2256
|
+
break;
|
|
2257
|
+
case "ampm": {
|
|
2258
|
+
const isAm = hours24 < 12;
|
|
2259
|
+
if (t.text === "a/p") out += isAm ? "a" : "p";
|
|
2260
|
+
else if (t.text === "A/P") out += isAm ? "A" : "P";
|
|
2261
|
+
else if (t.text === "am/pm") out += isAm ? "am" : "pm";
|
|
2262
|
+
else out += isAm ? "AM" : "PM";
|
|
2263
|
+
break;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
return out;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
1771
2270
|
// src/model/cell-reference.ts
|
|
2271
|
+
var EXCEL_MAX_ROWS = 1048576;
|
|
2272
|
+
var EXCEL_MAX_COLUMNS = 16384;
|
|
2273
|
+
function needsNormalizing(ref) {
|
|
2274
|
+
for (let i = 0; i < ref.length; i++) {
|
|
2275
|
+
const code = ref.charCodeAt(i);
|
|
2276
|
+
if (code === 36 || code >= 97 && code <= 122) return true;
|
|
2277
|
+
}
|
|
2278
|
+
return false;
|
|
2279
|
+
}
|
|
1772
2280
|
function parseCellRef(cellReference) {
|
|
1773
2281
|
if (!cellReference) {
|
|
1774
2282
|
throw new Error("Cell reference cannot be null or empty.");
|
|
1775
2283
|
}
|
|
1776
|
-
const ref = cellReference.replace(/\$/g, "").toUpperCase();
|
|
2284
|
+
const ref = needsNormalizing(cellReference) ? cellReference.replace(/\$/g, "").toUpperCase() : cellReference;
|
|
1777
2285
|
let i = 0;
|
|
1778
2286
|
let column = 0;
|
|
1779
2287
|
while (i < ref.length && ref.charCodeAt(i) >= 65 && ref.charCodeAt(i) <= 90) {
|
|
@@ -1819,6 +2327,11 @@ function columnNumber(letters) {
|
|
|
1819
2327
|
}
|
|
1820
2328
|
return col;
|
|
1821
2329
|
}
|
|
2330
|
+
function formatRangeRef(startRow, startColumn, endRow, endColumn) {
|
|
2331
|
+
const start = cellRefFromRowCol(startRow, startColumn);
|
|
2332
|
+
if (startRow === endRow && startColumn === endColumn) return start;
|
|
2333
|
+
return `${start}:${cellRefFromRowCol(endRow, endColumn)}`;
|
|
2334
|
+
}
|
|
1822
2335
|
function parseRangeRef(rangeReference) {
|
|
1823
2336
|
const parts = rangeReference.split(":");
|
|
1824
2337
|
if (parts.length !== 2) {
|
|
@@ -1839,56 +2352,54 @@ function parseRangeRef(rangeReference) {
|
|
|
1839
2352
|
};
|
|
1840
2353
|
}
|
|
1841
2354
|
|
|
1842
|
-
// src/model/
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
);
|
|
1869
|
-
}
|
|
1870
|
-
var DATE_FORMAT_IDS = /* @__PURE__ */ new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]);
|
|
1871
|
-
function isDateFormatId(formatId) {
|
|
1872
|
-
return DATE_FORMAT_IDS.has(formatId);
|
|
2355
|
+
// src/model/cell.ts
|
|
2356
|
+
function readCellValue(data, sharedStrings, styles) {
|
|
2357
|
+
const raw = data.rawValue;
|
|
2358
|
+
if (raw === void 0 || raw === "") return null;
|
|
2359
|
+
switch (data.dataType) {
|
|
2360
|
+
case "s": {
|
|
2361
|
+
const idx = parseInt(raw, 10);
|
|
2362
|
+
return sharedStrings.tryGetString(idx) ?? raw;
|
|
2363
|
+
}
|
|
2364
|
+
case "b":
|
|
2365
|
+
return raw === "1";
|
|
2366
|
+
case "str":
|
|
2367
|
+
return raw;
|
|
2368
|
+
default: {
|
|
2369
|
+
const num = parseFloat(raw);
|
|
2370
|
+
if (isNaN(num)) return raw;
|
|
2371
|
+
if (styles.isDateStyle(data.styleIndex)) {
|
|
2372
|
+
try {
|
|
2373
|
+
return fromOADate(num);
|
|
2374
|
+
} catch {
|
|
2375
|
+
return num;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return num;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
1873
2381
|
}
|
|
1874
|
-
function
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
|
|
2382
|
+
function buildCellXml(data) {
|
|
2383
|
+
const style = data.styleIndex > 0 ? ` s="${data.styleIndex}"` : "";
|
|
2384
|
+
const formulaXml = data.formula ? data.arrayRef ? `<f t="array" ref="${data.arrayRef}" aca="1" ca="1">${escXml(data.formula)}</f>` : `<f>${escXml(data.formula)}</f>` : "";
|
|
2385
|
+
const valueXml = data.rawValue !== void 0 && data.rawValue !== "" ? `<v>${escXml(data.rawValue)}</v>` : "";
|
|
2386
|
+
const type = data.dataType && (formulaXml || valueXml) ? ` t="${data.dataType}"` : "";
|
|
2387
|
+
if (!formulaXml && !valueXml && data.styleIndex === 0) return "";
|
|
2388
|
+
return `<c r="${data.reference}"${style}${type}>${formulaXml}${valueXml}</c>`;
|
|
1878
2389
|
}
|
|
1879
|
-
|
|
1880
|
-
// src/model/cell.ts
|
|
1881
|
-
var _data, _sharedStrings, _styles, _Cell_instances, isDateStyle_fn;
|
|
2390
|
+
var _data, _sharedStrings, _styles, _version, _Cell_instances, touch_fn;
|
|
1882
2391
|
var Cell = class {
|
|
1883
2392
|
/** @internal */
|
|
1884
|
-
constructor(data, sharedStrings, styles) {
|
|
2393
|
+
constructor(data, sharedStrings, styles, version) {
|
|
1885
2394
|
__privateAdd(this, _Cell_instances);
|
|
1886
2395
|
__privateAdd(this, _data);
|
|
1887
2396
|
__privateAdd(this, _sharedStrings);
|
|
1888
2397
|
__privateAdd(this, _styles);
|
|
2398
|
+
__privateAdd(this, _version);
|
|
1889
2399
|
__privateSet(this, _data, data);
|
|
1890
2400
|
__privateSet(this, _sharedStrings, sharedStrings);
|
|
1891
2401
|
__privateSet(this, _styles, styles);
|
|
2402
|
+
__privateSet(this, _version, version);
|
|
1892
2403
|
}
|
|
1893
2404
|
// ── Identity ───────────────────────────────────────────────────────────────
|
|
1894
2405
|
/** Cell reference string e.g. "A1". */
|
|
@@ -1904,6 +2415,7 @@ var Cell = class {
|
|
|
1904
2415
|
return parseCellRef(__privateGet(this, _data).reference).column;
|
|
1905
2416
|
}
|
|
1906
2417
|
setValue(value) {
|
|
2418
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
1907
2419
|
__privateGet(this, _data).formula = void 0;
|
|
1908
2420
|
if (value === null || value === void 0) {
|
|
1909
2421
|
__privateGet(this, _data).dataType = void 0;
|
|
@@ -1941,29 +2453,7 @@ var Cell = class {
|
|
|
1941
2453
|
* Date detection is based on the cell's number format.
|
|
1942
2454
|
*/
|
|
1943
2455
|
getValue() {
|
|
1944
|
-
|
|
1945
|
-
switch (__privateGet(this, _data).dataType) {
|
|
1946
|
-
case "s": {
|
|
1947
|
-
const idx = parseInt(__privateGet(this, _data).rawValue, 10);
|
|
1948
|
-
return __privateGet(this, _sharedStrings).tryGetString(idx) ?? __privateGet(this, _data).rawValue;
|
|
1949
|
-
}
|
|
1950
|
-
case "b":
|
|
1951
|
-
return __privateGet(this, _data).rawValue === "1";
|
|
1952
|
-
case "str":
|
|
1953
|
-
return __privateGet(this, _data).rawValue;
|
|
1954
|
-
default: {
|
|
1955
|
-
const num = parseFloat(__privateGet(this, _data).rawValue);
|
|
1956
|
-
if (isNaN(num)) return __privateGet(this, _data).rawValue;
|
|
1957
|
-
if (__privateMethod(this, _Cell_instances, isDateStyle_fn).call(this)) {
|
|
1958
|
-
try {
|
|
1959
|
-
return fromOADate(num);
|
|
1960
|
-
} catch {
|
|
1961
|
-
return num;
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
return num;
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
2456
|
+
return readCellValue(__privateGet(this, _data), __privateGet(this, _sharedStrings), __privateGet(this, _styles));
|
|
1967
2457
|
}
|
|
1968
2458
|
/**
|
|
1969
2459
|
* Returns the display text of the cell value (always a string).
|
|
@@ -1979,18 +2469,20 @@ var Cell = class {
|
|
|
1979
2469
|
}
|
|
1980
2470
|
/**
|
|
1981
2471
|
* Returns the display text with the cell's number format applied, e.g. a
|
|
1982
|
-
* value of 4.5 under `$#,##0.00` renders as `$4.50
|
|
2472
|
+
* value of 4.5 under `$#,##0.00` renders as `$4.50`, and a date under
|
|
2473
|
+
* `dd/mm/yyyy` as `31/01/2025`.
|
|
1983
2474
|
*
|
|
1984
|
-
* Falls back to {@link getText} for
|
|
1985
|
-
* outside the supported subset (fractions, scientific notation,
|
|
1986
|
-
* sections) — so the result is never a misleading
|
|
2475
|
+
* Falls back to {@link getText} for values with no format and for format
|
|
2476
|
+
* codes outside the supported subset (fractions, scientific notation,
|
|
2477
|
+
* conditional sections, elapsed time) — so the result is never a misleading
|
|
2478
|
+
* rendering.
|
|
1987
2479
|
*/
|
|
1988
2480
|
getFormattedText() {
|
|
1989
2481
|
const val = this.getValue();
|
|
1990
|
-
if (typeof val !== "number") return this.getText();
|
|
2482
|
+
if (typeof val !== "number" && !(val instanceof Date)) return this.getText();
|
|
1991
2483
|
const format = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).numberFormat;
|
|
1992
2484
|
if (!format) return this.getText();
|
|
1993
|
-
return formatNumberWithCode(val, format.formatCode) ?? this.getText();
|
|
2485
|
+
return val instanceof Date ? formatDateCode(val, format.formatCode) ?? this.getText() : formatNumberWithCode(val, format.formatCode) ?? this.getText();
|
|
1994
2486
|
}
|
|
1995
2487
|
// ── Formula API ────────────────────────────────────────────────────────────
|
|
1996
2488
|
/**
|
|
@@ -1999,6 +2491,7 @@ var Cell = class {
|
|
|
1999
2491
|
*/
|
|
2000
2492
|
setFormula(formula) {
|
|
2001
2493
|
if (!formula) throw new Error("Formula cannot be empty.");
|
|
2494
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2002
2495
|
__privateGet(this, _data).formula = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
2003
2496
|
__privateGet(this, _data).rawValue = void 0;
|
|
2004
2497
|
__privateGet(this, _data).dataType = void 0;
|
|
@@ -2011,6 +2504,7 @@ var Cell = class {
|
|
|
2011
2504
|
* plain scalar formula. Serialises as `<f t="array" ref="…">`.
|
|
2012
2505
|
*/
|
|
2013
2506
|
setArrayRef(spillRef) {
|
|
2507
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2014
2508
|
__privateGet(this, _data).arrayRef = spillRef ?? void 0;
|
|
2015
2509
|
return this;
|
|
2016
2510
|
}
|
|
@@ -2028,6 +2522,7 @@ var Cell = class {
|
|
|
2028
2522
|
* added to the shared-string table.
|
|
2029
2523
|
*/
|
|
2030
2524
|
setComputedValue(value) {
|
|
2525
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2031
2526
|
if (value === null || value === void 0) {
|
|
2032
2527
|
__privateGet(this, _data).dataType = void 0;
|
|
2033
2528
|
__privateGet(this, _data).rawValue = void 0;
|
|
@@ -2067,6 +2562,7 @@ var Cell = class {
|
|
|
2067
2562
|
}
|
|
2068
2563
|
/** Replaces the entire cell style. */
|
|
2069
2564
|
set style(value) {
|
|
2565
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2070
2566
|
__privateGet(this, _data).styleIndex = __privateGet(this, _styles).getOrCreateCellFormatIndex(value);
|
|
2071
2567
|
}
|
|
2072
2568
|
/**
|
|
@@ -2077,6 +2573,7 @@ var Cell = class {
|
|
|
2077
2573
|
* cell.applyStyle(s => s.setBold(true).setFontColor(ExcelColor.red))
|
|
2078
2574
|
*/
|
|
2079
2575
|
applyStyle(action) {
|
|
2576
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2080
2577
|
const s = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).clone();
|
|
2081
2578
|
action(s);
|
|
2082
2579
|
__privateGet(this, _data).styleIndex = __privateGet(this, _styles).getOrCreateCellFormatIndex(s);
|
|
@@ -2119,6 +2616,7 @@ var Cell = class {
|
|
|
2119
2616
|
// ── Clear ──────────────────────────────────────────────────────────────────
|
|
2120
2617
|
/** Clears the cell value and formula, preserving style. */
|
|
2121
2618
|
clear() {
|
|
2619
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2122
2620
|
__privateGet(this, _data).rawValue = void 0;
|
|
2123
2621
|
__privateGet(this, _data).dataType = void 0;
|
|
2124
2622
|
__privateGet(this, _data).formula = void 0;
|
|
@@ -2126,19 +2624,14 @@ var Cell = class {
|
|
|
2126
2624
|
}
|
|
2127
2625
|
/** Clears the cell value, formula, AND style. */
|
|
2128
2626
|
clearAll() {
|
|
2627
|
+
__privateMethod(this, _Cell_instances, touch_fn).call(this);
|
|
2129
2628
|
this.clear();
|
|
2130
2629
|
__privateGet(this, _data).styleIndex = 0;
|
|
2131
2630
|
}
|
|
2132
2631
|
// ── XML serialisation ──────────────────────────────────────────────────────
|
|
2133
2632
|
/** @internal Builds the <c> XML element string for this cell. */
|
|
2134
2633
|
toXml() {
|
|
2135
|
-
|
|
2136
|
-
const style = __privateGet(this, _data).styleIndex > 0 ? ` s="${__privateGet(this, _data).styleIndex}"` : "";
|
|
2137
|
-
const formulaXml = __privateGet(this, _data).formula ? __privateGet(this, _data).arrayRef ? `<f t="array" ref="${__privateGet(this, _data).arrayRef}" aca="1" ca="1">${escXml(__privateGet(this, _data).formula)}</f>` : `<f>${escXml(__privateGet(this, _data).formula)}</f>` : "";
|
|
2138
|
-
const valueXml = __privateGet(this, _data).rawValue !== void 0 && __privateGet(this, _data).rawValue !== "" ? `<v>${escXml(__privateGet(this, _data).rawValue)}</v>` : "";
|
|
2139
|
-
const type = __privateGet(this, _data).dataType && (formulaXml || valueXml) ? ` t="${__privateGet(this, _data).dataType}"` : "";
|
|
2140
|
-
if (!formulaXml && !valueXml && __privateGet(this, _data).styleIndex === 0) return "";
|
|
2141
|
-
return `<c r="${ref}"${style}${type}>${formulaXml}${valueXml}</c>`;
|
|
2634
|
+
return buildCellXml(__privateGet(this, _data));
|
|
2142
2635
|
}
|
|
2143
2636
|
/** @internal Parses a <c> XML element into CellData. */
|
|
2144
2637
|
static fromXmlElement(el, ref) {
|
|
@@ -2147,105 +2640,622 @@ var Cell = class {
|
|
|
2147
2640
|
let rawValue;
|
|
2148
2641
|
let formula;
|
|
2149
2642
|
let arrayRef;
|
|
2643
|
+
let sharedIndex;
|
|
2150
2644
|
for (const child of el.children) {
|
|
2151
2645
|
const tag = child.tagName.replace(/^.*:/, "");
|
|
2152
2646
|
if (tag === "v") rawValue = child.textContent;
|
|
2153
2647
|
if (tag === "f") {
|
|
2154
2648
|
formula = child.textContent;
|
|
2155
|
-
|
|
2156
|
-
|
|
2649
|
+
const attrs = child.attributes;
|
|
2650
|
+
const type = attrs?.["t"];
|
|
2651
|
+
if (type === "array" && attrs?.["ref"]) {
|
|
2652
|
+
arrayRef = attrs["ref"];
|
|
2653
|
+
}
|
|
2654
|
+
if (type === "shared" && attrs?.["si"] !== void 0) {
|
|
2655
|
+
const si = parseInt(attrs["si"], 10);
|
|
2656
|
+
if (!Number.isNaN(si)) sharedIndex = si;
|
|
2157
2657
|
}
|
|
2158
2658
|
}
|
|
2159
2659
|
}
|
|
2160
|
-
|
|
2660
|
+
if (formula === "") formula = void 0;
|
|
2661
|
+
return { reference: ref, dataType, rawValue, formula, arrayRef, styleIndex, sharedIndex };
|
|
2161
2662
|
}
|
|
2162
2663
|
};
|
|
2163
2664
|
_data = new WeakMap();
|
|
2164
2665
|
_sharedStrings = new WeakMap();
|
|
2165
2666
|
_styles = new WeakMap();
|
|
2667
|
+
_version = new WeakMap();
|
|
2166
2668
|
_Cell_instances = new WeakSet();
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
if (!style.numberFormat) return false;
|
|
2171
|
-
if (isDateFormatId(style.numberFormat.formatId)) return true;
|
|
2172
|
-
return isDateFormatCode(style.numberFormat.formatCode);
|
|
2669
|
+
/** Records that this cell's stored data changed. */
|
|
2670
|
+
touch_fn = function() {
|
|
2671
|
+
if (__privateGet(this, _version)) __privateGet(this, _version).v++;
|
|
2173
2672
|
};
|
|
2174
2673
|
function escXml(s) {
|
|
2175
2674
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2176
2675
|
}
|
|
2177
2676
|
|
|
2178
|
-
// src/model/
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
__privateSet(this, _endRow, endRow);
|
|
2195
|
-
__privateSet(this, _endColumn, endColumn);
|
|
2196
|
-
}
|
|
2197
|
-
/** The range address e.g. "A1:B10". */
|
|
2198
|
-
get address() {
|
|
2199
|
-
return __privateGet(this, _address);
|
|
2200
|
-
}
|
|
2201
|
-
/** Number of rows in the range. */
|
|
2202
|
-
get rowCount() {
|
|
2203
|
-
return __privateGet(this, _endRow) - __privateGet(this, _startRow) + 1;
|
|
2204
|
-
}
|
|
2205
|
-
/** Number of columns in the range. */
|
|
2206
|
-
get columnCount() {
|
|
2207
|
-
return __privateGet(this, _endColumn) - __privateGet(this, _startColumn) + 1;
|
|
2677
|
+
// src/model/ref-shift.ts
|
|
2678
|
+
function shiftIndex(index, shift) {
|
|
2679
|
+
if (shift.kind === "insert") {
|
|
2680
|
+
return index >= shift.at ? index + shift.count : index;
|
|
2681
|
+
}
|
|
2682
|
+
const bandEnd = shift.at + shift.count - 1;
|
|
2683
|
+
if (index < shift.at) return index;
|
|
2684
|
+
if (index <= bandEnd) return null;
|
|
2685
|
+
return index - shift.count;
|
|
2686
|
+
}
|
|
2687
|
+
function shiftSpan(start, end, shift) {
|
|
2688
|
+
if (shift.kind === "insert") {
|
|
2689
|
+
return {
|
|
2690
|
+
start: start >= shift.at ? start + shift.count : start,
|
|
2691
|
+
end: end >= shift.at ? end + shift.count : end
|
|
2692
|
+
};
|
|
2208
2693
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2694
|
+
const bandEnd = shift.at + shift.count - 1;
|
|
2695
|
+
const newStart = start < shift.at ? start : start <= bandEnd ? shift.at : start - shift.count;
|
|
2696
|
+
const newEnd = end < shift.at ? end : end <= bandEnd ? shift.at - 1 : end - shift.count;
|
|
2697
|
+
return newEnd < newStart ? null : { start: newStart, end: newEnd };
|
|
2698
|
+
}
|
|
2699
|
+
function shiftRangeRef(ref, shift) {
|
|
2700
|
+
const [startRef, endRef] = ref.split(":");
|
|
2701
|
+
const start = parseCellRef(startRef);
|
|
2702
|
+
const end = endRef !== void 0 ? parseCellRef(endRef) : start;
|
|
2703
|
+
const rows = shift.dim === "row" ? shiftSpan(start.row, end.row, shift) : { start: start.row, end: end.row };
|
|
2704
|
+
const cols = shift.dim === "col" ? shiftSpan(start.column, end.column, shift) : { start: start.column, end: end.column };
|
|
2705
|
+
if (!rows || !cols) return null;
|
|
2706
|
+
const startOut = cellRefFromRowCol(rows.start, cols.start);
|
|
2707
|
+
if (endRef === void 0) return startOut;
|
|
2708
|
+
return `${startOut}:${cellRefFromRowCol(rows.end, cols.end)}`;
|
|
2709
|
+
}
|
|
2710
|
+
function shiftSqref(sqref, shift) {
|
|
2711
|
+
const surviving = sqref.split(/\s+/).filter(Boolean).map((part) => shiftRangeRef(part, shift)).filter((part) => part !== null);
|
|
2712
|
+
return surviving.length > 0 ? surviving.join(" ") : null;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
// src/model/formula-ref-shift.ts
|
|
2716
|
+
var MAX_ROW = EXCEL_MAX_ROWS;
|
|
2717
|
+
var MAX_COL = EXCEL_MAX_COLUMNS;
|
|
2718
|
+
function isWordChar(code) {
|
|
2719
|
+
return code >= 65 && code <= 90 || // A-Z
|
|
2720
|
+
code >= 97 && code <= 122 || // a-z
|
|
2721
|
+
code >= 48 && code <= 57 || // 0-9
|
|
2722
|
+
code === 95 || // _
|
|
2723
|
+
code === 46 || // .
|
|
2724
|
+
code === 36;
|
|
2725
|
+
}
|
|
2726
|
+
function parseCellComponent(word) {
|
|
2727
|
+
const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
|
|
2728
|
+
if (!m) return null;
|
|
2729
|
+
const col = columnNumber(m[2]);
|
|
2730
|
+
const row = parseInt(m[4], 10);
|
|
2731
|
+
if (col > MAX_COL || row < 1 || row > MAX_ROW) return null;
|
|
2732
|
+
return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
|
|
2733
|
+
}
|
|
2734
|
+
function parseColComponent(word) {
|
|
2735
|
+
const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
|
|
2736
|
+
if (!m) return null;
|
|
2737
|
+
const col = columnNumber(m[2]);
|
|
2738
|
+
if (col > MAX_COL) return null;
|
|
2739
|
+
return { abs: m[1] === "$", col };
|
|
2740
|
+
}
|
|
2741
|
+
function parseRowComponent(word) {
|
|
2742
|
+
const m = /^(\$?)(\d+)$/.exec(word);
|
|
2743
|
+
if (!m) return null;
|
|
2744
|
+
const row = parseInt(m[2], 10);
|
|
2745
|
+
if (row < 1 || row > MAX_ROW) return null;
|
|
2746
|
+
return { abs: m[1] === "$", row };
|
|
2747
|
+
}
|
|
2748
|
+
function emitCell(c, row, col) {
|
|
2749
|
+
return `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
|
|
2750
|
+
}
|
|
2751
|
+
function shiftFormulaRefs(formula, shift, formulaSheet) {
|
|
2752
|
+
const n = formula.length;
|
|
2753
|
+
const editedSheet = shift.sheetName.toUpperCase();
|
|
2754
|
+
const unqualifiedRewritable = formulaSheet.toUpperCase() === editedSheet;
|
|
2755
|
+
const isRowShift = shift.dim === "row";
|
|
2756
|
+
let out = "";
|
|
2757
|
+
let flushed = 0;
|
|
2758
|
+
let changed = false;
|
|
2759
|
+
let hasRefError = false;
|
|
2760
|
+
let i = 0;
|
|
2761
|
+
const replace = (start, text) => {
|
|
2762
|
+
if (text.length === i - start && formula.startsWith(text, start)) return;
|
|
2763
|
+
out += formula.slice(flushed, start) + text;
|
|
2764
|
+
flushed = i;
|
|
2765
|
+
changed = true;
|
|
2766
|
+
};
|
|
2767
|
+
const readWord = () => {
|
|
2768
|
+
let j = i;
|
|
2769
|
+
while (j < n && isWordChar(formula.charCodeAt(j))) j++;
|
|
2770
|
+
const w = formula.slice(i, j);
|
|
2771
|
+
i = j;
|
|
2772
|
+
return w;
|
|
2773
|
+
};
|
|
2774
|
+
const readQuoted = () => {
|
|
2775
|
+
let j = i + 1;
|
|
2776
|
+
while (j < n) {
|
|
2777
|
+
if (formula[j] === "'") {
|
|
2778
|
+
if (formula[j + 1] === "'") {
|
|
2779
|
+
j += 2;
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
j++;
|
|
2783
|
+
break;
|
|
2221
2784
|
}
|
|
2785
|
+
j++;
|
|
2222
2786
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2787
|
+
const q = formula.slice(i, j);
|
|
2788
|
+
i = j;
|
|
2789
|
+
return q;
|
|
2790
|
+
};
|
|
2791
|
+
const consumeRef = (refStart, rewritable) => {
|
|
2792
|
+
const first = readWord();
|
|
2793
|
+
let firstCell = parseCellComponent(first);
|
|
2794
|
+
let firstCol = firstCell ? null : parseColComponent(first);
|
|
2795
|
+
let firstRow = firstCell || firstCol ? null : parseRowComponent(first);
|
|
2796
|
+
let second = null;
|
|
2797
|
+
let secondCell = null;
|
|
2798
|
+
let secondCol = null;
|
|
2799
|
+
let secondRow = null;
|
|
2800
|
+
if (formula[i] === ":" && i + 1 < n && isWordChar(formula.charCodeAt(i + 1))) {
|
|
2801
|
+
const save = i;
|
|
2802
|
+
i++;
|
|
2803
|
+
const w = readWord();
|
|
2804
|
+
if (firstCell) secondCell = parseCellComponent(w);
|
|
2805
|
+
else if (firstCol) secondCol = parseColComponent(w);
|
|
2806
|
+
else if (firstRow) secondRow = parseRowComponent(w);
|
|
2807
|
+
if (secondCell || secondCol || secondRow) {
|
|
2808
|
+
second = w;
|
|
2809
|
+
} else {
|
|
2810
|
+
i = save;
|
|
2234
2811
|
}
|
|
2235
|
-
result.push(row);
|
|
2236
2812
|
}
|
|
2237
|
-
return
|
|
2813
|
+
if (second === null && formula[i] === "(") return;
|
|
2814
|
+
const dead = () => {
|
|
2815
|
+
if (formula[i] === "#") i++;
|
|
2816
|
+
replace(refStart, "#REF!");
|
|
2817
|
+
hasRefError = true;
|
|
2818
|
+
};
|
|
2819
|
+
if (second !== null) {
|
|
2820
|
+
if (!rewritable) return;
|
|
2821
|
+
if (firstCell && secondCell) {
|
|
2822
|
+
const a = firstCell, b = secondCell;
|
|
2823
|
+
const rows = isRowShift ? shiftSpan(a.row, b.row, shift) : { start: a.row, end: b.row };
|
|
2824
|
+
const cols = isRowShift ? { start: a.col, end: b.col } : shiftSpan(a.col, b.col, shift);
|
|
2825
|
+
if (!rows || !cols) {
|
|
2826
|
+
dead();
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
replace(refStart, `${emitCell(a, rows.start, cols.start)}:${emitCell(b, rows.end, cols.end)}`);
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
if (firstCol && secondCol) {
|
|
2833
|
+
if (isRowShift) return;
|
|
2834
|
+
const span = shiftSpan(firstCol.col, secondCol.col, shift);
|
|
2835
|
+
if (!span) {
|
|
2836
|
+
dead();
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
const a = firstCol.abs ? "$" : "";
|
|
2840
|
+
const b = secondCol.abs ? "$" : "";
|
|
2841
|
+
replace(refStart, `${a}${columnLetter(span.start)}:${b}${columnLetter(span.end)}`);
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
if (firstRow && secondRow) {
|
|
2845
|
+
if (!isRowShift) return;
|
|
2846
|
+
const span = shiftSpan(firstRow.row, secondRow.row, shift);
|
|
2847
|
+
if (!span) {
|
|
2848
|
+
dead();
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
const a = firstRow.abs ? "$" : "";
|
|
2852
|
+
const b = secondRow.abs ? "$" : "";
|
|
2853
|
+
replace(refStart, `${a}${span.start}:${b}${span.end}`);
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
if (!firstCell || !rewritable) return;
|
|
2859
|
+
const row = isRowShift ? shiftIndex(firstCell.row, shift) : firstCell.row;
|
|
2860
|
+
const col = isRowShift ? firstCell.col : shiftIndex(firstCell.col, shift);
|
|
2861
|
+
if (row === null || col === null) {
|
|
2862
|
+
dead();
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
replace(refStart, emitCell(firstCell, row, col));
|
|
2866
|
+
};
|
|
2867
|
+
while (i < n) {
|
|
2868
|
+
const ch = formula[i];
|
|
2869
|
+
if (ch === '"') {
|
|
2870
|
+
let j = i + 1;
|
|
2871
|
+
while (j < n) {
|
|
2872
|
+
if (formula[j] === '"') {
|
|
2873
|
+
if (formula[j + 1] === '"') {
|
|
2874
|
+
j += 2;
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2877
|
+
j++;
|
|
2878
|
+
break;
|
|
2879
|
+
}
|
|
2880
|
+
j++;
|
|
2881
|
+
}
|
|
2882
|
+
i = j;
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
if (ch === "'") {
|
|
2886
|
+
const quoted = readQuoted();
|
|
2887
|
+
if (formula[i] === "!") {
|
|
2888
|
+
i++;
|
|
2889
|
+
const name = quoted.slice(1, -1);
|
|
2890
|
+
const rewritable = name.indexOf(":") === -1 && (name.indexOf("''") === -1 ? name : name.replace(/''/g, "'")).toUpperCase() === editedSheet;
|
|
2891
|
+
consumeRef(i, rewritable);
|
|
2892
|
+
}
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
if (isWordChar(formula.charCodeAt(i))) {
|
|
2896
|
+
const start = i;
|
|
2897
|
+
const word = readWord();
|
|
2898
|
+
if (formula[i] === "!") {
|
|
2899
|
+
i++;
|
|
2900
|
+
consumeRef(i, word.toUpperCase() === editedSheet);
|
|
2901
|
+
continue;
|
|
2902
|
+
}
|
|
2903
|
+
if (formula[i] === ":") {
|
|
2904
|
+
const save = i;
|
|
2905
|
+
i++;
|
|
2906
|
+
const w2 = i < n && isWordChar(formula.charCodeAt(i)) ? readWord() : "";
|
|
2907
|
+
if (w2 && formula[i] === "!") {
|
|
2908
|
+
i++;
|
|
2909
|
+
consumeRef(i, false);
|
|
2910
|
+
continue;
|
|
2911
|
+
}
|
|
2912
|
+
i = save;
|
|
2913
|
+
}
|
|
2914
|
+
if (formula[i] === "[") {
|
|
2915
|
+
let depth = 0;
|
|
2916
|
+
let j = i;
|
|
2917
|
+
while (j < n) {
|
|
2918
|
+
if (formula[j] === "[") depth++;
|
|
2919
|
+
else if (formula[j] === "]" && --depth === 0) {
|
|
2920
|
+
j++;
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
j++;
|
|
2924
|
+
}
|
|
2925
|
+
i = j;
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
i = start;
|
|
2929
|
+
consumeRef(start, unqualifiedRewritable);
|
|
2930
|
+
continue;
|
|
2931
|
+
}
|
|
2932
|
+
i++;
|
|
2933
|
+
}
|
|
2934
|
+
if (!changed) return { text: formula, changed: false, hasRefError };
|
|
2935
|
+
return { text: out + formula.slice(flushed), changed: true, hasRefError };
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
// src/model/formula-ref-translate.ts
|
|
2939
|
+
function isWordChar2(code) {
|
|
2940
|
+
return code >= 65 && code <= 90 || // A-Z
|
|
2941
|
+
code >= 97 && code <= 122 || // a-z
|
|
2942
|
+
code >= 48 && code <= 57 || // 0-9
|
|
2943
|
+
code === 95 || // _
|
|
2944
|
+
code === 46 || // .
|
|
2945
|
+
code === 36;
|
|
2946
|
+
}
|
|
2947
|
+
function parseCellComponent2(word) {
|
|
2948
|
+
const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
|
|
2949
|
+
if (!m) return null;
|
|
2950
|
+
const col = columnNumber(m[2]);
|
|
2951
|
+
const row = parseInt(m[4], 10);
|
|
2952
|
+
if (col > EXCEL_MAX_COLUMNS || row < 1 || row > EXCEL_MAX_ROWS) return null;
|
|
2953
|
+
return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
|
|
2954
|
+
}
|
|
2955
|
+
function parseColComponent2(word) {
|
|
2956
|
+
const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
|
|
2957
|
+
if (!m) return null;
|
|
2958
|
+
const col = columnNumber(m[2]);
|
|
2959
|
+
return col > EXCEL_MAX_COLUMNS ? null : { abs: m[1] === "$", col };
|
|
2960
|
+
}
|
|
2961
|
+
function parseRowComponent2(word) {
|
|
2962
|
+
const m = /^(\$?)(\d+)$/.exec(word);
|
|
2963
|
+
if (!m) return null;
|
|
2964
|
+
const row = parseInt(m[2], 10);
|
|
2965
|
+
return row < 1 || row > EXCEL_MAX_ROWS ? null : { abs: m[1] === "$", row };
|
|
2966
|
+
}
|
|
2967
|
+
function move(index, delta, abs, max) {
|
|
2968
|
+
if (abs || delta === 0) return index;
|
|
2969
|
+
const moved = index + delta;
|
|
2970
|
+
return moved < 1 || moved > max ? null : moved;
|
|
2971
|
+
}
|
|
2972
|
+
function translateFormulaRefs(formula, dRow, dCol) {
|
|
2973
|
+
if (dRow === 0 && dCol === 0) return { text: formula, hasRefError: false };
|
|
2974
|
+
const n = formula.length;
|
|
2975
|
+
let out = "";
|
|
2976
|
+
let flushed = 0;
|
|
2977
|
+
let hasRefError = false;
|
|
2978
|
+
let i = 0;
|
|
2979
|
+
const replace = (start, text) => {
|
|
2980
|
+
if (text.length === i - start && formula.startsWith(text, start)) return;
|
|
2981
|
+
out += formula.slice(flushed, start) + text;
|
|
2982
|
+
flushed = i;
|
|
2983
|
+
};
|
|
2984
|
+
const readWord = () => {
|
|
2985
|
+
let j = i;
|
|
2986
|
+
while (j < n && isWordChar2(formula.charCodeAt(j))) j++;
|
|
2987
|
+
const w = formula.slice(i, j);
|
|
2988
|
+
i = j;
|
|
2989
|
+
return w;
|
|
2990
|
+
};
|
|
2991
|
+
const readQuoted = () => {
|
|
2992
|
+
let j = i + 1;
|
|
2993
|
+
while (j < n) {
|
|
2994
|
+
if (formula[j] === "'") {
|
|
2995
|
+
if (formula[j + 1] === "'") {
|
|
2996
|
+
j += 2;
|
|
2997
|
+
continue;
|
|
2998
|
+
}
|
|
2999
|
+
j++;
|
|
3000
|
+
break;
|
|
3001
|
+
}
|
|
3002
|
+
j++;
|
|
3003
|
+
}
|
|
3004
|
+
const q = formula.slice(i, j);
|
|
3005
|
+
i = j;
|
|
3006
|
+
return q;
|
|
3007
|
+
};
|
|
3008
|
+
const emitCell2 = (c, row, col) => `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
|
|
3009
|
+
const consumeRef = (refStart, rewrite = true) => {
|
|
3010
|
+
const first = readWord();
|
|
3011
|
+
const firstCell = parseCellComponent2(first);
|
|
3012
|
+
const firstCol = firstCell ? null : parseColComponent2(first);
|
|
3013
|
+
const firstRow = firstCell || firstCol ? null : parseRowComponent2(first);
|
|
3014
|
+
if (!firstCell && !firstCol && !firstRow) return;
|
|
3015
|
+
let secondCell = null;
|
|
3016
|
+
let secondCol = null;
|
|
3017
|
+
let secondRow = null;
|
|
3018
|
+
let isRange = false;
|
|
3019
|
+
if (formula[i] === ":" && i + 1 < n && isWordChar2(formula.charCodeAt(i + 1))) {
|
|
3020
|
+
const save = i;
|
|
3021
|
+
i++;
|
|
3022
|
+
const w = readWord();
|
|
3023
|
+
if (firstCell) secondCell = parseCellComponent2(w);
|
|
3024
|
+
else if (firstCol) secondCol = parseColComponent2(w);
|
|
3025
|
+
else if (firstRow) secondRow = parseRowComponent2(w);
|
|
3026
|
+
if (secondCell || secondCol || secondRow) isRange = true;
|
|
3027
|
+
else i = save;
|
|
3028
|
+
}
|
|
3029
|
+
if (!isRange && formula[i] === "(") return;
|
|
3030
|
+
if (!rewrite) {
|
|
3031
|
+
if (formula[i] === "#") i++;
|
|
3032
|
+
return;
|
|
3033
|
+
}
|
|
3034
|
+
const dead = () => {
|
|
3035
|
+
if (formula[i] === "#") i++;
|
|
3036
|
+
replace(refStart, "#REF!");
|
|
3037
|
+
hasRefError = true;
|
|
3038
|
+
};
|
|
3039
|
+
if (isRange) {
|
|
3040
|
+
if (firstCell && secondCell) {
|
|
3041
|
+
const r1 = move(firstCell.row, dRow, firstCell.rowAbs, EXCEL_MAX_ROWS);
|
|
3042
|
+
const c1 = move(firstCell.col, dCol, firstCell.colAbs, EXCEL_MAX_COLUMNS);
|
|
3043
|
+
const r2 = move(secondCell.row, dRow, secondCell.rowAbs, EXCEL_MAX_ROWS);
|
|
3044
|
+
const c2 = move(secondCell.col, dCol, secondCell.colAbs, EXCEL_MAX_COLUMNS);
|
|
3045
|
+
if (r1 === null || c1 === null || r2 === null || c2 === null) {
|
|
3046
|
+
dead();
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
replace(refStart, `${emitCell2(firstCell, r1, c1)}:${emitCell2(secondCell, r2, c2)}`);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
if (firstCol && secondCol) {
|
|
3053
|
+
const c1 = move(firstCol.col, dCol, firstCol.abs, EXCEL_MAX_COLUMNS);
|
|
3054
|
+
const c2 = move(secondCol.col, dCol, secondCol.abs, EXCEL_MAX_COLUMNS);
|
|
3055
|
+
if (c1 === null || c2 === null) {
|
|
3056
|
+
dead();
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
replace(refStart, `${firstCol.abs ? "$" : ""}${columnLetter(c1)}:${secondCol.abs ? "$" : ""}${columnLetter(c2)}`);
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
if (firstRow && secondRow) {
|
|
3063
|
+
const r1 = move(firstRow.row, dRow, firstRow.abs, EXCEL_MAX_ROWS);
|
|
3064
|
+
const r2 = move(secondRow.row, dRow, secondRow.abs, EXCEL_MAX_ROWS);
|
|
3065
|
+
if (r1 === null || r2 === null) {
|
|
3066
|
+
dead();
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
replace(refStart, `${firstRow.abs ? "$" : ""}${r1}:${secondRow.abs ? "$" : ""}${r2}`);
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
if (!firstCell) return;
|
|
3075
|
+
const row = move(firstCell.row, dRow, firstCell.rowAbs, EXCEL_MAX_ROWS);
|
|
3076
|
+
const col = move(firstCell.col, dCol, firstCell.colAbs, EXCEL_MAX_COLUMNS);
|
|
3077
|
+
if (row === null || col === null) {
|
|
3078
|
+
dead();
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
replace(refStart, emitCell2(firstCell, row, col));
|
|
3082
|
+
};
|
|
3083
|
+
while (i < n) {
|
|
3084
|
+
const ch = formula[i];
|
|
3085
|
+
if (ch === '"') {
|
|
3086
|
+
let j = i + 1;
|
|
3087
|
+
while (j < n) {
|
|
3088
|
+
if (formula[j] === '"') {
|
|
3089
|
+
if (formula[j + 1] === '"') {
|
|
3090
|
+
j += 2;
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
j++;
|
|
3094
|
+
break;
|
|
3095
|
+
}
|
|
3096
|
+
j++;
|
|
3097
|
+
}
|
|
3098
|
+
i = j;
|
|
3099
|
+
continue;
|
|
3100
|
+
}
|
|
3101
|
+
if (ch === "'") {
|
|
3102
|
+
readQuoted();
|
|
3103
|
+
if (formula[i] === "!") {
|
|
3104
|
+
i++;
|
|
3105
|
+
consumeRef(i);
|
|
3106
|
+
}
|
|
3107
|
+
continue;
|
|
3108
|
+
}
|
|
3109
|
+
if (isWordChar2(formula.charCodeAt(i))) {
|
|
3110
|
+
const start = i;
|
|
3111
|
+
readWord();
|
|
3112
|
+
if (formula[i] === "!") {
|
|
3113
|
+
i++;
|
|
3114
|
+
consumeRef(i);
|
|
3115
|
+
continue;
|
|
3116
|
+
}
|
|
3117
|
+
if (formula[i] === ":") {
|
|
3118
|
+
const save = i;
|
|
3119
|
+
i++;
|
|
3120
|
+
const w2 = i < n && isWordChar2(formula.charCodeAt(i)) ? readWord() : "";
|
|
3121
|
+
if (w2 && formula[i] === "!") {
|
|
3122
|
+
i++;
|
|
3123
|
+
consumeRef(i, false);
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
i = save;
|
|
3127
|
+
}
|
|
3128
|
+
if (formula[i] === "[") {
|
|
3129
|
+
let depth = 0;
|
|
3130
|
+
let j = i;
|
|
3131
|
+
while (j < n) {
|
|
3132
|
+
if (formula[j] === "[") depth++;
|
|
3133
|
+
else if (formula[j] === "]" && --depth === 0) {
|
|
3134
|
+
j++;
|
|
3135
|
+
break;
|
|
3136
|
+
}
|
|
3137
|
+
j++;
|
|
3138
|
+
}
|
|
3139
|
+
i = j;
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
i = start;
|
|
3143
|
+
consumeRef(start);
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
i++;
|
|
3147
|
+
}
|
|
3148
|
+
return { text: out + formula.slice(flushed), hasRefError };
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
// src/model/range.ts
|
|
3152
|
+
var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn, forEachExisting_fn;
|
|
3153
|
+
var Range = class {
|
|
3154
|
+
constructor(sheet, rangeReference) {
|
|
3155
|
+
__privateAdd(this, _Range_instances);
|
|
3156
|
+
__privateAdd(this, _sheet);
|
|
3157
|
+
__privateAdd(this, _startRow);
|
|
3158
|
+
__privateAdd(this, _startColumn);
|
|
3159
|
+
__privateAdd(this, _endRow);
|
|
3160
|
+
__privateAdd(this, _endColumn);
|
|
3161
|
+
__privateAdd(this, _address);
|
|
3162
|
+
__privateSet(this, _sheet, sheet);
|
|
3163
|
+
__privateSet(this, _address, rangeReference.toUpperCase());
|
|
3164
|
+
const { startRow, startColumn, endRow, endColumn } = parseRangeRef(rangeReference);
|
|
3165
|
+
__privateSet(this, _startRow, startRow);
|
|
3166
|
+
__privateSet(this, _startColumn, startColumn);
|
|
3167
|
+
__privateSet(this, _endRow, endRow);
|
|
3168
|
+
__privateSet(this, _endColumn, endColumn);
|
|
3169
|
+
}
|
|
3170
|
+
/** The range address e.g. "A1:B10". */
|
|
3171
|
+
get address() {
|
|
3172
|
+
return __privateGet(this, _address);
|
|
3173
|
+
}
|
|
3174
|
+
/** Number of rows in the range. */
|
|
3175
|
+
get rowCount() {
|
|
3176
|
+
return __privateGet(this, _endRow) - __privateGet(this, _startRow) + 1;
|
|
3177
|
+
}
|
|
3178
|
+
/** Number of columns in the range. */
|
|
3179
|
+
get columnCount() {
|
|
3180
|
+
return __privateGet(this, _endColumn) - __privateGet(this, _startColumn) + 1;
|
|
3181
|
+
}
|
|
3182
|
+
// ── Value bulk operations ──────────────────────────────────────────────────
|
|
3183
|
+
/**
|
|
3184
|
+
* Sets values from a 2D array.
|
|
3185
|
+
* Values that exceed the range boundary are silently ignored.
|
|
3186
|
+
*/
|
|
3187
|
+
setValues(values) {
|
|
3188
|
+
for (let r = 0; r < values.length && __privateGet(this, _startRow) + r <= __privateGet(this, _endRow); r++) {
|
|
3189
|
+
const rowValues = values[r];
|
|
3190
|
+
for (let c = 0; c < rowValues.length && __privateGet(this, _startColumn) + c <= __privateGet(this, _endColumn); c++) {
|
|
3191
|
+
const cell = __privateGet(this, _sheet).getCell(__privateGet(this, _startRow) + r, __privateGet(this, _startColumn) + c);
|
|
3192
|
+
const val = rowValues[c];
|
|
3193
|
+
cell.setValue(val);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
/**
|
|
3198
|
+
* Gets all cell values as a 2D array (row-major order).
|
|
3199
|
+
*/
|
|
3200
|
+
getValues() {
|
|
3201
|
+
const rowCount = this.rowCount;
|
|
3202
|
+
const columnCount = this.columnCount;
|
|
3203
|
+
const result = new Array(rowCount);
|
|
3204
|
+
for (let r = 0; r < rowCount; r++) result[r] = new Array(columnCount).fill(null);
|
|
3205
|
+
const startRow = __privateGet(this, _startRow);
|
|
3206
|
+
const startColumn = __privateGet(this, _startColumn);
|
|
3207
|
+
__privateGet(this, _sheet).forEachCellIn(
|
|
3208
|
+
startRow,
|
|
3209
|
+
startColumn,
|
|
3210
|
+
__privateGet(this, _endRow),
|
|
3211
|
+
__privateGet(this, _endColumn),
|
|
3212
|
+
(cell, row, column) => {
|
|
3213
|
+
result[row - startRow][column - startColumn] = cell.getValue();
|
|
3214
|
+
}
|
|
3215
|
+
);
|
|
3216
|
+
return result;
|
|
2238
3217
|
}
|
|
2239
3218
|
/** Clears all cells in the range (values only, preserves style). */
|
|
2240
3219
|
clear() {
|
|
2241
|
-
__privateMethod(this, _Range_instances,
|
|
3220
|
+
__privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clear());
|
|
2242
3221
|
}
|
|
2243
|
-
/**
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
3222
|
+
/** Clears values AND styles of all cells in the range. */
|
|
3223
|
+
clearAll() {
|
|
3224
|
+
__privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clearAll());
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Moves the whole range so its top-left cell lands on `destTopLeft`,
|
|
3228
|
+
* carrying values, formulas, styles and spill anchors. The vacated cells
|
|
3229
|
+
* are cleared and the destination block is fully replaced (empty source
|
|
3230
|
+
* cells clear their destination). Overlapping source/destination is safe.
|
|
3231
|
+
* Formula text is NOT adjusted for the new position.
|
|
3232
|
+
*
|
|
3233
|
+
* @example
|
|
3234
|
+
* ws.getRange('A1:B3').moveTo('D1');
|
|
3235
|
+
*/
|
|
3236
|
+
moveTo(destTopLeft) {
|
|
3237
|
+
const dest = parseCellRef(destTopLeft.replace(/\$/g, "").toUpperCase());
|
|
3238
|
+
const deltaRow = dest.row - __privateGet(this, _startRow);
|
|
3239
|
+
const deltaCol = dest.column - __privateGet(this, _startColumn);
|
|
3240
|
+
if (deltaRow === 0 && deltaCol === 0) return;
|
|
3241
|
+
if (__privateGet(this, _endRow) + deltaRow > EXCEL_MAX_ROWS || __privateGet(this, _endColumn) + deltaCol > EXCEL_MAX_COLUMNS) {
|
|
3242
|
+
throw new RangeError("Destination extends past the sheet limits.");
|
|
3243
|
+
}
|
|
3244
|
+
const rowOrder = deltaRow > 0 ? { from: __privateGet(this, _endRow), to: __privateGet(this, _startRow), step: -1 } : { from: __privateGet(this, _startRow), to: __privateGet(this, _endRow), step: 1 };
|
|
3245
|
+
const colOrder = deltaCol > 0 ? { from: __privateGet(this, _endColumn), to: __privateGet(this, _startColumn), step: -1 } : { from: __privateGet(this, _startColumn), to: __privateGet(this, _endColumn), step: 1 };
|
|
3246
|
+
for (let r = rowOrder.from; deltaRow > 0 ? r >= rowOrder.to : r <= rowOrder.to; r += rowOrder.step) {
|
|
3247
|
+
for (let c = colOrder.from; deltaCol > 0 ? c >= colOrder.to : c <= colOrder.to; c += colOrder.step) {
|
|
3248
|
+
__privateGet(this, _sheet).moveCellRC(r, c, r + deltaRow, c + deltaCol);
|
|
3249
|
+
}
|
|
2247
3250
|
}
|
|
2248
3251
|
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Auto-fits all columns in the range. Measures every column in one sparse
|
|
3254
|
+
* sweep rather than re-scanning the sheet per column.
|
|
3255
|
+
*/
|
|
3256
|
+
autoFit() {
|
|
3257
|
+
__privateGet(this, _sheet).autoFitColumns(__privateGet(this, _startColumn), __privateGet(this, _endColumn));
|
|
3258
|
+
}
|
|
2249
3259
|
// ── Style bulk operations ──────────────────────────────────────────────────
|
|
2250
3260
|
/** Applies a CellStyle to all cells in the range. Returns this for chaining. */
|
|
2251
3261
|
setStyle(style) {
|
|
@@ -2318,9 +3328,57 @@ forEach_fn = function(action) {
|
|
|
2318
3328
|
}
|
|
2319
3329
|
}
|
|
2320
3330
|
};
|
|
3331
|
+
/**
|
|
3332
|
+
* Like #forEach but only visits cells that already exist — used by the
|
|
3333
|
+
* clearing operations so they never materialise empty cells, and so the cost
|
|
3334
|
+
* tracks the populated cells rather than the area of the range.
|
|
3335
|
+
*/
|
|
3336
|
+
forEachExisting_fn = function(action) {
|
|
3337
|
+
__privateGet(this, _sheet).forEachCellIn(
|
|
3338
|
+
__privateGet(this, _startRow),
|
|
3339
|
+
__privateGet(this, _startColumn),
|
|
3340
|
+
__privateGet(this, _endRow),
|
|
3341
|
+
__privateGet(this, _endColumn),
|
|
3342
|
+
(cell) => action(cell)
|
|
3343
|
+
);
|
|
3344
|
+
};
|
|
2321
3345
|
|
|
2322
3346
|
// src/model/worksheet.ts
|
|
2323
|
-
var
|
|
3347
|
+
var PROTECTION_DEFAULTS = {
|
|
3348
|
+
allowSelectLockedCells: true,
|
|
3349
|
+
allowSelectUnlockedCells: true,
|
|
3350
|
+
allowFormatCells: false,
|
|
3351
|
+
allowFormatColumns: false,
|
|
3352
|
+
allowFormatRows: false,
|
|
3353
|
+
allowInsertRows: false,
|
|
3354
|
+
allowInsertColumns: false,
|
|
3355
|
+
allowInsertHyperlinks: false,
|
|
3356
|
+
allowDeleteRows: false,
|
|
3357
|
+
allowDeleteColumns: false,
|
|
3358
|
+
allowSort: false,
|
|
3359
|
+
allowAutoFilter: false,
|
|
3360
|
+
allowPivotTables: false,
|
|
3361
|
+
allowEditObjects: false,
|
|
3362
|
+
allowEditScenarios: false
|
|
3363
|
+
};
|
|
3364
|
+
var PROTECTION_ATTRS = [
|
|
3365
|
+
["allowSelectLockedCells", "selectLockedCells"],
|
|
3366
|
+
["allowSelectUnlockedCells", "selectUnlockedCells"],
|
|
3367
|
+
["allowFormatCells", "formatCells"],
|
|
3368
|
+
["allowFormatColumns", "formatColumns"],
|
|
3369
|
+
["allowFormatRows", "formatRows"],
|
|
3370
|
+
["allowInsertRows", "insertRows"],
|
|
3371
|
+
["allowInsertColumns", "insertColumns"],
|
|
3372
|
+
["allowInsertHyperlinks", "insertHyperlinks"],
|
|
3373
|
+
["allowDeleteRows", "deleteRows"],
|
|
3374
|
+
["allowDeleteColumns", "deleteColumns"],
|
|
3375
|
+
["allowSort", "sort"],
|
|
3376
|
+
["allowAutoFilter", "autoFilter"],
|
|
3377
|
+
["allowPivotTables", "pivotTables"],
|
|
3378
|
+
["allowEditObjects", "objects"],
|
|
3379
|
+
["allowEditScenarios", "scenarios"]
|
|
3380
|
+
];
|
|
3381
|
+
var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _protection, _protectionCredentials, _hyperlinks, _comments, _commentsDirty, _pendingHyperlinkRels, _preservedTail, _preservedRels, _version2, _usedBoundsMemo, _xmlMemo, _Worksheet_instances, touch_fn2, findCellData_fn, computeUsedBounds_fn, 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, resolveSharedFormulas_fn, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, splitColSpan_fn, insertColDef_fn, findColDefIndex_fn, findColDef_fn, pruneColDefs_fn, getOrCreateRow_fn;
|
|
2324
3382
|
var Worksheet = class {
|
|
2325
3383
|
/** @internal */
|
|
2326
3384
|
constructor(name, sharedStrings, styles) {
|
|
@@ -2336,16 +3394,52 @@ var Worksheet = class {
|
|
|
2336
3394
|
__privateAdd(this, _dataValidations, []);
|
|
2337
3395
|
__privateAdd(this, _visibility, "visible");
|
|
2338
3396
|
__privateAdd(this, _drawings, []);
|
|
3397
|
+
__privateAdd(this, _protection);
|
|
3398
|
+
/**
|
|
3399
|
+
* Password/hash attributes of a protection element read from a file, kept
|
|
3400
|
+
* verbatim so a protected sheet round-trips with its password intact. This
|
|
3401
|
+
* library never creates them: see {@link protect}.
|
|
3402
|
+
*/
|
|
3403
|
+
__privateAdd(this, _protectionCredentials, "");
|
|
3404
|
+
/** Hyperlinks keyed by their normalised ref, in insertion order. */
|
|
3405
|
+
__privateAdd(this, _hyperlinks, /* @__PURE__ */ new Map());
|
|
3406
|
+
/** Comments keyed by their normalised cell ref, in insertion order. */
|
|
3407
|
+
__privateAdd(this, _comments, /* @__PURE__ */ new Map());
|
|
3408
|
+
/**
|
|
3409
|
+
* True once the comment set has been modified through the API. Until then a
|
|
3410
|
+
* sheet loaded from a file keeps its original comments and VML parts verbatim,
|
|
3411
|
+
* so untouched files round-trip byte-for-byte; from then on this sheet owns
|
|
3412
|
+
* them and regenerates both. See {@link setComment}.
|
|
3413
|
+
*/
|
|
3414
|
+
__privateAdd(this, _commentsDirty, false);
|
|
3415
|
+
/** Hyperlink ref → relationship id, awaiting resolution against the rels part. */
|
|
3416
|
+
__privateAdd(this, _pendingHyperlinkRels, /* @__PURE__ */ new Map());
|
|
2339
3417
|
// Round-trip state captured when this sheet was loaded from a file: the
|
|
2340
3418
|
// relationship-bearing leaf elements of the worksheet part, and the sheet's
|
|
2341
3419
|
// original relationships. Re-emitted on save so drawings, images and legacy
|
|
2342
3420
|
// comment anchors survive open→save. See io/preserved-parts.ts.
|
|
2343
3421
|
__privateAdd(this, _preservedTail, []);
|
|
2344
3422
|
__privateAdd(this, _preservedRels, []);
|
|
3423
|
+
/**
|
|
3424
|
+
* Mutation counter for this sheet, shared with every {@link Cell} view it
|
|
3425
|
+
* hands out (a Cell writes directly into the stored CellData). Everything
|
|
3426
|
+
* derived from the sheet's contents — the used-range memo, the cached
|
|
3427
|
+
* worksheet XML — is keyed on it, so every mutator must bump it. The test
|
|
3428
|
+
* `worksheet-version.test.ts` walks the public surface to keep that true.
|
|
3429
|
+
*/
|
|
3430
|
+
__privateAdd(this, _version2, { v: 0 });
|
|
3431
|
+
/** Memoised {@link usedBounds}, valid while `#version.v` is unchanged. */
|
|
3432
|
+
__privateAdd(this, _usedBoundsMemo);
|
|
3433
|
+
/** Memoised {@link buildXml} output, valid while `#version.v` is unchanged. */
|
|
3434
|
+
__privateAdd(this, _xmlMemo);
|
|
2345
3435
|
__privateSet(this, _name, name);
|
|
2346
3436
|
__privateSet(this, _sharedStrings2, sharedStrings);
|
|
2347
3437
|
__privateSet(this, _styles2, styles);
|
|
2348
3438
|
}
|
|
3439
|
+
/** @internal Current mutation count — see {@link #version}. */
|
|
3440
|
+
get version() {
|
|
3441
|
+
return __privateGet(this, _version2).v;
|
|
3442
|
+
}
|
|
2349
3443
|
// ── Identity ───────────────────────────────────────────────────────────────
|
|
2350
3444
|
get name() {
|
|
2351
3445
|
return __privateGet(this, _name);
|
|
@@ -2355,24 +3449,39 @@ var Worksheet = class {
|
|
|
2355
3449
|
__privateSet(this, _name, value);
|
|
2356
3450
|
}
|
|
2357
3451
|
getCell(refOrRow, column) {
|
|
2358
|
-
|
|
2359
|
-
|
|
3452
|
+
let ref;
|
|
3453
|
+
let row;
|
|
3454
|
+
if (typeof refOrRow === "string") {
|
|
3455
|
+
ref = normalizeRefFast(refOrRow);
|
|
3456
|
+
row = parseCellRef(ref).row;
|
|
3457
|
+
} else {
|
|
3458
|
+
ref = cellRefFromRowCol(refOrRow, column);
|
|
3459
|
+
row = refOrRow;
|
|
3460
|
+
}
|
|
2360
3461
|
const rowData = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, row);
|
|
2361
3462
|
let cellData = rowData.cells.get(ref);
|
|
2362
3463
|
if (!cellData) {
|
|
3464
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2363
3465
|
cellData = { reference: ref, styleIndex: 0 };
|
|
2364
3466
|
rowData.cells.set(ref, cellData);
|
|
2365
3467
|
}
|
|
2366
|
-
return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
|
|
3468
|
+
return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2));
|
|
2367
3469
|
}
|
|
2368
3470
|
tryGetCell(refOrRow, column) {
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
3471
|
+
let rowData;
|
|
3472
|
+
let ref;
|
|
3473
|
+
if (typeof refOrRow === "string") {
|
|
3474
|
+
ref = normalizeRefFast(refOrRow);
|
|
3475
|
+
rowData = __privateGet(this, _rows).get(parseCellRef(ref).row);
|
|
3476
|
+
if (!rowData) return null;
|
|
3477
|
+
} else {
|
|
3478
|
+
rowData = __privateGet(this, _rows).get(refOrRow);
|
|
3479
|
+
if (!rowData) return null;
|
|
3480
|
+
ref = cellRefFromRowCol(refOrRow, column);
|
|
3481
|
+
}
|
|
2373
3482
|
const cellData = rowData.cells.get(ref);
|
|
2374
3483
|
if (!cellData) return null;
|
|
2375
|
-
return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
|
|
3484
|
+
return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2));
|
|
2376
3485
|
}
|
|
2377
3486
|
cell(refOrRow, column) {
|
|
2378
3487
|
return typeof refOrRow === "string" ? this.getCell(refOrRow) : this.getCell(refOrRow, column);
|
|
@@ -2384,42 +3493,158 @@ var Worksheet = class {
|
|
|
2384
3493
|
}
|
|
2385
3494
|
/** Gets the raw value of a cell (for formula evaluation). */
|
|
2386
3495
|
getCellValue(reference) {
|
|
2387
|
-
|
|
3496
|
+
const cd = __privateMethod(this, _Worksheet_instances, findCellData_fn).call(this, reference);
|
|
3497
|
+
return cd ? readCellValue(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)) : null;
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Value of a cell addressed by 1-based row/column. The reference-string
|
|
3501
|
+
* overload has to build (and the callee re-parse) a string per cell; a range
|
|
3502
|
+
* read walking a rectangle already knows the coordinates, and on a real
|
|
3503
|
+
* workbook that is tens of millions of strings per recalculation.
|
|
3504
|
+
*/
|
|
3505
|
+
getCellValueAt(row, column) {
|
|
3506
|
+
const rowData = __privateGet(this, _rows).get(row);
|
|
3507
|
+
if (!rowData) return null;
|
|
3508
|
+
const cd = rowData.cells.get(cellRefFromRowCol(row, column));
|
|
3509
|
+
return cd ? readCellValue(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)) : null;
|
|
2388
3510
|
}
|
|
2389
3511
|
/** Formula text (without leading '=') of a cell, or null if it has none. */
|
|
2390
3512
|
getCellFormula(reference) {
|
|
2391
|
-
return this.
|
|
3513
|
+
return __privateMethod(this, _Worksheet_instances, findCellData_fn).call(this, reference)?.formula ?? null;
|
|
2392
3514
|
}
|
|
2393
3515
|
/** Spill range (e.g. "A1:A3") of a dynamic-array anchor, or null. Supports A1#. */
|
|
2394
3516
|
getSpillRange(reference) {
|
|
2395
|
-
return this.
|
|
3517
|
+
return __privateMethod(this, _Worksheet_instances, findCellData_fn).call(this, reference)?.arrayRef ?? null;
|
|
3518
|
+
}
|
|
3519
|
+
/**
|
|
3520
|
+
* Iterates the populated rows of the sheet in ascending row order, yielding
|
|
3521
|
+
* each row's 1-based index and its cells (left to right). Rows and cells
|
|
3522
|
+
* that were never written are skipped, so a sparse sheet iterates in
|
|
3523
|
+
* O(populated cells) regardless of its bounds.
|
|
3524
|
+
*
|
|
3525
|
+
* @example
|
|
3526
|
+
* for (const { rowIndex, cells } of ws.rows()) {
|
|
3527
|
+
* for (const cell of cells) console.log(rowIndex, cell.reference, cell.getValue());
|
|
3528
|
+
* }
|
|
3529
|
+
*/
|
|
3530
|
+
*rows() {
|
|
3531
|
+
const populated = [...__privateGet(this, _rows).values()].filter((row) => row.cells.size > 0).sort((a, b) => a.rowIndex - b.rowIndex);
|
|
3532
|
+
for (const row of populated) {
|
|
3533
|
+
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), __privateGet(this, _version2)));
|
|
3534
|
+
yield { rowIndex: row.rowIndex, cells };
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* Iterates every populated cell of the sheet in row-major order. Sparse
|
|
3539
|
+
* counterpart to scanning {@link usedBounds} with {@link tryGetCell}.
|
|
3540
|
+
*
|
|
3541
|
+
* @example
|
|
3542
|
+
* for (const cell of ws.cells()) console.log(cell.reference, cell.getValue());
|
|
3543
|
+
*/
|
|
3544
|
+
*cells() {
|
|
3545
|
+
for (const { cells } of this.rows()) yield* cells;
|
|
3546
|
+
}
|
|
3547
|
+
/**
|
|
3548
|
+
* @internal Visits the populated cells inside a rectangle, passing the
|
|
3549
|
+
* coordinates it already knows so the callback never has to parse a
|
|
3550
|
+
* reference. Backs the Range operations that must not create cells; probing
|
|
3551
|
+
* every coordinate would cost O(area) even on an empty sheet.
|
|
3552
|
+
*/
|
|
3553
|
+
forEachCellIn(startRow, startColumn, endRow, endColumn, action) {
|
|
3554
|
+
const visit = (row) => {
|
|
3555
|
+
for (const cd of row.cells.values()) {
|
|
3556
|
+
const col = columnFromRef(cd.reference, refSplit(cd.reference));
|
|
3557
|
+
if (col < startColumn || col > endColumn) continue;
|
|
3558
|
+
action(new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2)), row.rowIndex, col);
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
3561
|
+
if (endRow - startRow + 1 <= __privateGet(this, _rows).size) {
|
|
3562
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
3563
|
+
const row = __privateGet(this, _rows).get(r);
|
|
3564
|
+
if (row) visit(row);
|
|
3565
|
+
}
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3569
|
+
if (row.rowIndex >= startRow && row.rowIndex <= endRow) visit(row);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* @internal Moves a cell by coordinates, skipping the reference parsing that
|
|
3574
|
+
* the public {@link moveCell} has to do. Used by bulk range moves.
|
|
3575
|
+
*/
|
|
3576
|
+
moveCellRC(fromRow, fromColumn, toRow, toColumn) {
|
|
3577
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3578
|
+
if (fromRow === toRow && fromColumn === toColumn) return;
|
|
3579
|
+
const fromRef = cellRefFromRowCol(fromRow, fromColumn);
|
|
3580
|
+
const toRef = cellRefFromRowCol(toRow, toColumn);
|
|
3581
|
+
const src = __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, fromRow, fromRef);
|
|
3582
|
+
if (!src) {
|
|
3583
|
+
__privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, toRow, toRef);
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3586
|
+
const moved = { ...src, reference: toRef };
|
|
3587
|
+
if (moved.arrayRef) {
|
|
3588
|
+
moved.arrayRef = translateRangeRef(moved.arrayRef, toRow - fromRow, toColumn - fromColumn);
|
|
3589
|
+
}
|
|
3590
|
+
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toRow).cells.set(toRef, moved);
|
|
2396
3591
|
}
|
|
2397
3592
|
/**
|
|
2398
3593
|
* Returns the 1-based extent of populated cells (largest row and column that
|
|
2399
3594
|
* contain data). Returns zeros for an empty sheet. Useful for snapshotting a
|
|
2400
|
-
* sheet into a dense 2D array
|
|
3595
|
+
* sheet into a dense 2D array — though {@link rows} and {@link cells} are
|
|
3596
|
+
* cheaper when a dense shape is not actually required.
|
|
2401
3597
|
*/
|
|
2402
3598
|
get usedBounds() {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
for (const ref of row.cells.keys()) {
|
|
2409
|
-
const col = parseCellRef(ref).column;
|
|
2410
|
-
if (col > columnCount) columnCount = col;
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
return { rowCount, columnCount };
|
|
3599
|
+
const memo = __privateGet(this, _usedBoundsMemo);
|
|
3600
|
+
if (memo && memo.at === __privateGet(this, _version2).v) return memo.value;
|
|
3601
|
+
const value = __privateMethod(this, _Worksheet_instances, computeUsedBounds_fn).call(this);
|
|
3602
|
+
__privateSet(this, _usedBoundsMemo, { at: __privateGet(this, _version2).v, value });
|
|
3603
|
+
return value;
|
|
2414
3604
|
}
|
|
2415
3605
|
// ── Layout ─────────────────────────────────────────────────────────────────
|
|
2416
3606
|
/** Sets the width of a column (1-based index). */
|
|
2417
3607
|
setColumnWidth(columnIndex, width) {
|
|
3608
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2418
3609
|
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
2419
3610
|
if (width <= 0) throw new RangeError("Width must be > 0.");
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
3611
|
+
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 });
|
|
3612
|
+
def.width = width;
|
|
3613
|
+
def.customWidth = true;
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Removes the explicit width of a column so it falls back to the sheet
|
|
3617
|
+
* default. Counterpart to {@link setColumnWidth}; afterwards
|
|
3618
|
+
* {@link getColumnWidth} returns `undefined`. A hidden column stays hidden.
|
|
3619
|
+
*/
|
|
3620
|
+
resetColumnWidth(columnIndex) {
|
|
3621
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3622
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3623
|
+
const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
|
|
3624
|
+
if (!def) return;
|
|
3625
|
+
def.width = void 0;
|
|
3626
|
+
def.customWidth = false;
|
|
3627
|
+
__privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
|
|
3628
|
+
}
|
|
3629
|
+
/**
|
|
3630
|
+
* Shows or hides a column (1-based index). Hidden columns round-trip to the
|
|
3631
|
+
* file as `<col hidden="1">` and keep any explicit width.
|
|
3632
|
+
*/
|
|
3633
|
+
setColumnHidden(columnIndex, hidden = true) {
|
|
3634
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3635
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3636
|
+
const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
|
|
3637
|
+
if (def) {
|
|
3638
|
+
def.hidden = hidden;
|
|
3639
|
+
__privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
|
|
3640
|
+
} else if (hidden) {
|
|
3641
|
+
__privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false, hidden: true });
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
/** True if the given 1-based column is hidden. */
|
|
3645
|
+
isColumnHidden(columnIndex) {
|
|
3646
|
+
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
3647
|
+
return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.hidden === true;
|
|
2423
3648
|
}
|
|
2424
3649
|
/**
|
|
2425
3650
|
* Returns the explicit width of a column (1-based index), or `undefined` when
|
|
@@ -2429,25 +3654,34 @@ var Worksheet = class {
|
|
|
2429
3654
|
*/
|
|
2430
3655
|
getColumnWidth(columnIndex) {
|
|
2431
3656
|
if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
|
|
2432
|
-
return
|
|
3657
|
+
return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.width;
|
|
2433
3658
|
}
|
|
2434
3659
|
/** Auto-fits column width based on content (approximate). */
|
|
2435
3660
|
autoFitColumn(columnIndex) {
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
3661
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3662
|
+
this.autoFitColumns(columnIndex, columnIndex);
|
|
3663
|
+
}
|
|
3664
|
+
/**
|
|
3665
|
+
* Auto-fits an inclusive span of columns (1-based) in a single pass over the
|
|
3666
|
+
* populated cells, instead of re-scanning the sheet once per column.
|
|
3667
|
+
*/
|
|
3668
|
+
autoFitColumns(firstColumn, lastColumn) {
|
|
3669
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3670
|
+
if (firstColumn < 1) throw new RangeError("Column index must be >= 1.");
|
|
3671
|
+
if (lastColumn < firstColumn) return;
|
|
3672
|
+
const maxLen = /* @__PURE__ */ new Map();
|
|
3673
|
+
this.forEachCellIn(1, firstColumn, EXCEL_MAX_ROWS, lastColumn, (cell, _row, column) => {
|
|
3674
|
+
const len = cell.getText().length;
|
|
3675
|
+
if (len > (maxLen.get(column) ?? 0)) maxLen.set(column, len);
|
|
3676
|
+
});
|
|
3677
|
+
for (let col = firstColumn; col <= lastColumn; col++) {
|
|
3678
|
+
const len = Math.max(maxLen.get(col) ?? 0, 8);
|
|
3679
|
+
this.setColumnWidth(col, Math.min(len * 1.2 + 2, 255));
|
|
2446
3680
|
}
|
|
2447
|
-
this.setColumnWidth(columnIndex, Math.min(maxLen * 1.2 + 2, 255));
|
|
2448
3681
|
}
|
|
2449
3682
|
/** Sets the height of a row (1-based index). */
|
|
2450
3683
|
setRowHeight(rowIndex, height) {
|
|
3684
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2451
3685
|
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
2452
3686
|
if (height <= 0) throw new RangeError("Height must be > 0.");
|
|
2453
3687
|
const row = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, rowIndex);
|
|
@@ -2462,8 +3696,23 @@ var Worksheet = class {
|
|
|
2462
3696
|
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
2463
3697
|
return __privateGet(this, _rows).get(rowIndex)?.height;
|
|
2464
3698
|
}
|
|
3699
|
+
/**
|
|
3700
|
+
* Removes the explicit height of a row so it falls back to the sheet
|
|
3701
|
+
* default. Counterpart to {@link setRowHeight}; afterwards
|
|
3702
|
+
* {@link getRowHeight} returns `undefined`. A hidden row stays hidden.
|
|
3703
|
+
*/
|
|
3704
|
+
resetRowHeight(rowIndex) {
|
|
3705
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3706
|
+
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
3707
|
+
const row = __privateGet(this, _rows).get(rowIndex);
|
|
3708
|
+
if (!row) return;
|
|
3709
|
+
row.height = void 0;
|
|
3710
|
+
row.customHeight = void 0;
|
|
3711
|
+
if (row.cells.size === 0 && !row.hidden) __privateGet(this, _rows).delete(rowIndex);
|
|
3712
|
+
}
|
|
2465
3713
|
/** Shows or hides a row (1-based index). Hidden rows affect SUBTOTAL(101-111). */
|
|
2466
3714
|
setRowHidden(rowIndex, hidden = true) {
|
|
3715
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2467
3716
|
if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
|
|
2468
3717
|
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, rowIndex).hidden = hidden;
|
|
2469
3718
|
}
|
|
@@ -2471,13 +3720,153 @@ var Worksheet = class {
|
|
|
2471
3720
|
isRowHidden(rowIndex) {
|
|
2472
3721
|
return __privateGet(this, _rows).get(rowIndex)?.hidden === true;
|
|
2473
3722
|
}
|
|
3723
|
+
/**
|
|
3724
|
+
* The 1-based indices of every hidden row, ascending. Sparse counterpart to
|
|
3725
|
+
* probing {@link isRowHidden} across {@link usedBounds}.
|
|
3726
|
+
*/
|
|
3727
|
+
get hiddenRows() {
|
|
3728
|
+
const result = [];
|
|
3729
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
3730
|
+
if (row.hidden) result.push(row.rowIndex);
|
|
3731
|
+
}
|
|
3732
|
+
return result.sort((a, b) => a - b);
|
|
3733
|
+
}
|
|
3734
|
+
/**
|
|
3735
|
+
* The 1-based indices of every hidden column, ascending. Counterpart to
|
|
3736
|
+
* {@link isColumnHidden}, in the same spirit as {@link hiddenRows}.
|
|
3737
|
+
*/
|
|
3738
|
+
get hiddenColumns() {
|
|
3739
|
+
const result = [];
|
|
3740
|
+
for (const def of __privateGet(this, _cols)) {
|
|
3741
|
+
if (!def.hidden) continue;
|
|
3742
|
+
for (let c = def.min; c <= def.max; c++) result.push(c);
|
|
3743
|
+
}
|
|
3744
|
+
return result.sort((a, b) => a - b);
|
|
3745
|
+
}
|
|
3746
|
+
// ── Structural editing ─────────────────────────────────────────────────────
|
|
3747
|
+
/**
|
|
3748
|
+
* Inserts `count` empty rows starting at row `at` (1-based). Everything at
|
|
3749
|
+
* or below `at` moves down: cell values, formulas (references are rewritten
|
|
3750
|
+
* the way Excel does), styles, row heights, hidden flags, merges,
|
|
3751
|
+
* autofilter and data-validation ranges.
|
|
3752
|
+
*
|
|
3753
|
+
* Frozen panes are a view property and stay put. Drawing/chart anchors
|
|
3754
|
+
* preserved from a loaded file are opaque XML and do NOT shift — images
|
|
3755
|
+
* keep their absolute position (known limitation).
|
|
3756
|
+
*
|
|
3757
|
+
* When formulas on OTHER sheets reference this sheet, use the
|
|
3758
|
+
* Workbook-level counterpart so they are rewritten too, or call
|
|
3759
|
+
* `applyExternalShift` on each sheet yourself. If a RecalcEngine is
|
|
3760
|
+
* attached, notify it afterwards (e.g. `engine.onRowsInserted(...)`).
|
|
3761
|
+
*
|
|
3762
|
+
* @example
|
|
3763
|
+
* ws.insertRows(3, 2); // pushes row 3 and below down by two rows
|
|
3764
|
+
*/
|
|
3765
|
+
insertRows(at, count = 1) {
|
|
3766
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3767
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "insert", at, count);
|
|
3768
|
+
}
|
|
3769
|
+
/**
|
|
3770
|
+
* Deletes `count` rows starting at row `at` (1-based). Rows below move up;
|
|
3771
|
+
* formulas referencing the deleted band get `#REF!` (fully inside) or
|
|
3772
|
+
* shrink (partially inside), exactly like Excel. See {@link insertRows}
|
|
3773
|
+
* for the shifting rules and known limitations.
|
|
3774
|
+
*/
|
|
3775
|
+
deleteRows(at, count = 1) {
|
|
3776
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3777
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "delete", at, count);
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* Inserts `count` empty columns starting at column `at` (1-based).
|
|
3781
|
+
* Column-level counterpart to {@link insertRows}; widths and hidden flags
|
|
3782
|
+
* shift with the columns.
|
|
3783
|
+
*/
|
|
3784
|
+
insertColumns(at, count = 1) {
|
|
3785
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3786
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "insert", at, count);
|
|
3787
|
+
}
|
|
3788
|
+
/**
|
|
3789
|
+
* Deletes `count` columns starting at column `at` (1-based).
|
|
3790
|
+
* Column-level counterpart to {@link deleteRows}.
|
|
3791
|
+
*/
|
|
3792
|
+
deleteColumns(at, count = 1) {
|
|
3793
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3794
|
+
return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "delete", at, count);
|
|
3795
|
+
}
|
|
3796
|
+
/**
|
|
3797
|
+
* @internal Rewrites formulas on THIS sheet after a structural edit on a
|
|
3798
|
+
* DIFFERENT sheet (named by `shift.sheetName`), so qualified references
|
|
3799
|
+
* like `'Data'!A5` stay correct. Cell positions here do not change.
|
|
3800
|
+
*/
|
|
3801
|
+
applyExternalShift(shift) {
|
|
3802
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3803
|
+
return { refErrors: __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift) };
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Moves a cell's full contents — value, formula, style and spill anchor —
|
|
3807
|
+
* to another cell, overwriting the destination and clearing the source.
|
|
3808
|
+
* A missing source clears the destination. This is a literal move: formula
|
|
3809
|
+
* text is NOT adjusted for the new position.
|
|
3810
|
+
*
|
|
3811
|
+
* @example
|
|
3812
|
+
* ws.moveCell('A1', 'C5');
|
|
3813
|
+
*/
|
|
3814
|
+
moveCell(from, to) {
|
|
3815
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3816
|
+
const fromRef = normalizeRef(from);
|
|
3817
|
+
const toRef = normalizeRef(to);
|
|
3818
|
+
if (fromRef === toRef) return;
|
|
3819
|
+
const src = __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, fromRef);
|
|
3820
|
+
if (!src) {
|
|
3821
|
+
__privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
const fromPos = parseCellRef(fromRef);
|
|
3825
|
+
const toPos = parseCellRef(toRef);
|
|
3826
|
+
const moved = { ...src, reference: toRef };
|
|
3827
|
+
if (moved.arrayRef) {
|
|
3828
|
+
moved.arrayRef = translateRangeRef(
|
|
3829
|
+
moved.arrayRef,
|
|
3830
|
+
toPos.row - fromPos.row,
|
|
3831
|
+
toPos.column - fromPos.column
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3834
|
+
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, moved);
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Copies a cell's value, formula and style to another cell, overwriting the
|
|
3838
|
+
* destination and leaving the source untouched. A missing source clears the
|
|
3839
|
+
* destination. This is a literal copy: formula references are NOT adjusted,
|
|
3840
|
+
* and the spill anchor (`arrayRef`) is not copied — a recalc engine
|
|
3841
|
+
* re-establishes it.
|
|
3842
|
+
*
|
|
3843
|
+
* @example
|
|
3844
|
+
* ws.copyCell('A1', 'C5');
|
|
3845
|
+
*/
|
|
3846
|
+
copyCell(from, to) {
|
|
3847
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3848
|
+
const fromRef = normalizeRef(from);
|
|
3849
|
+
const toRef = normalizeRef(to);
|
|
3850
|
+
if (fromRef === toRef) return;
|
|
3851
|
+
const srcRow = __privateGet(this, _rows).get(parseCellRef(fromRef).row);
|
|
3852
|
+
const src = srcRow?.cells.get(fromRef);
|
|
3853
|
+
if (!src) {
|
|
3854
|
+
__privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
const toPos = parseCellRef(toRef);
|
|
3858
|
+
const copied = { ...src, reference: toRef, arrayRef: void 0 };
|
|
3859
|
+
__privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, copied);
|
|
3860
|
+
}
|
|
2474
3861
|
/** Merges cells in the given range (e.g. "A1:B2"). */
|
|
2475
3862
|
mergeCells(rangeReference) {
|
|
3863
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2476
3864
|
if (!rangeReference) throw new Error("Range reference cannot be empty.");
|
|
2477
3865
|
__privateGet(this, _mergeCells).push({ ref: rangeReference });
|
|
2478
3866
|
}
|
|
2479
3867
|
/** Removes a merge for the given range reference. */
|
|
2480
3868
|
unmergeCells(rangeReference) {
|
|
3869
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2481
3870
|
__privateSet(this, _mergeCells, __privateGet(this, _mergeCells).filter((m) => m.ref !== rangeReference));
|
|
2482
3871
|
}
|
|
2483
3872
|
/**
|
|
@@ -2494,12 +3883,14 @@ var Worksheet = class {
|
|
|
2494
3883
|
* @param column Number of columns to freeze (0 = none)
|
|
2495
3884
|
*/
|
|
2496
3885
|
freezePanes(row, column) {
|
|
3886
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2497
3887
|
if (row < 0) throw new RangeError("Row must be >= 0.");
|
|
2498
3888
|
if (column < 0) throw new RangeError("Column must be >= 0.");
|
|
2499
3889
|
__privateSet(this, _pane, row === 0 && column === 0 ? void 0 : { row, column });
|
|
2500
3890
|
}
|
|
2501
3891
|
/** Sets auto-filter on the given range. */
|
|
2502
3892
|
setAutoFilter(rangeReference) {
|
|
3893
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2503
3894
|
__privateSet(this, _autoFilter, { ref: rangeReference });
|
|
2504
3895
|
}
|
|
2505
3896
|
/**
|
|
@@ -2508,15 +3899,136 @@ var Worksheet = class {
|
|
|
2508
3899
|
* @param listFormula Formula for the dropdown list (e.g. "'Sheet2'!$A$1:$A$10")
|
|
2509
3900
|
*/
|
|
2510
3901
|
addDropdownValidation(cellRange, listFormula) {
|
|
3902
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2511
3903
|
__privateGet(this, _dataValidations).push({ sqref: cellRange, formula: listFormula });
|
|
2512
3904
|
}
|
|
2513
3905
|
/** Sets the visibility state of this worksheet. */
|
|
2514
3906
|
setVisibility(state) {
|
|
3907
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2515
3908
|
__privateSet(this, _visibility, state);
|
|
2516
3909
|
}
|
|
2517
3910
|
get visibility() {
|
|
2518
3911
|
return __privateGet(this, _visibility);
|
|
2519
3912
|
}
|
|
3913
|
+
// ── Sheet protection ───────────────────────────────────────────────────────
|
|
3914
|
+
/**
|
|
3915
|
+
* Turns on sheet protection, optionally relaxing individual permissions.
|
|
3916
|
+
* Cells are locked by default in Excel, so protecting a sheet makes all of
|
|
3917
|
+
* them read-only; clear a cell style's `locked` flag to leave a cell editable.
|
|
3918
|
+
*
|
|
3919
|
+
* Excel's sheet protection is a UI guard, **not** security: the contents stay
|
|
3920
|
+
* readable to anything that opens the file. This library therefore never
|
|
3921
|
+
* creates a protection password. A password read from an existing file is
|
|
3922
|
+
* preserved so the sheet round-trips unchanged.
|
|
3923
|
+
*
|
|
3924
|
+
* @example
|
|
3925
|
+
* ws.protect(); // lock everything
|
|
3926
|
+
* ws.protect({ allowSort: true, allowAutoFilter: true });
|
|
3927
|
+
*/
|
|
3928
|
+
protect(options) {
|
|
3929
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3930
|
+
__privateSet(this, _protection, { ...PROTECTION_DEFAULTS, ...options });
|
|
3931
|
+
}
|
|
3932
|
+
/** Turns off sheet protection, discarding any password read from a file. */
|
|
3933
|
+
unprotect() {
|
|
3934
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3935
|
+
__privateSet(this, _protection, void 0);
|
|
3936
|
+
__privateSet(this, _protectionCredentials, "");
|
|
3937
|
+
}
|
|
3938
|
+
/** True when this sheet is protected. */
|
|
3939
|
+
get isProtected() {
|
|
3940
|
+
return __privateGet(this, _protection) !== void 0;
|
|
3941
|
+
}
|
|
3942
|
+
/** The active permissions, or `undefined` when the sheet is not protected. */
|
|
3943
|
+
get protection() {
|
|
3944
|
+
return __privateGet(this, _protection) ? { ...__privateGet(this, _protection) } : void 0;
|
|
3945
|
+
}
|
|
3946
|
+
// ── Hyperlinks ─────────────────────────────────────────────────────────────
|
|
3947
|
+
/**
|
|
3948
|
+
* Attaches a hyperlink to a cell or range. Give `target` for an external
|
|
3949
|
+
* destination or `location` for one inside the workbook; passing both keeps
|
|
3950
|
+
* the external target and uses the location as its fragment, which is how
|
|
3951
|
+
* Excel links to an anchor within a document.
|
|
3952
|
+
*
|
|
3953
|
+
* A cell's displayed text still comes from its own value — set that
|
|
3954
|
+
* separately; `display` only overrides what Excel shows in the edit bar.
|
|
3955
|
+
*
|
|
3956
|
+
* @example
|
|
3957
|
+
* ws.getCell('A1').setValue('Anthropic');
|
|
3958
|
+
* ws.setHyperlink('A1', { target: 'https://anthropic.com', tooltip: 'Open site' });
|
|
3959
|
+
* ws.setHyperlink('B1', { location: "'Sheet2'!A1" });
|
|
3960
|
+
*/
|
|
3961
|
+
setHyperlink(ref, link) {
|
|
3962
|
+
if (!link.target && !link.location) {
|
|
3963
|
+
throw new Error("A hyperlink needs a target (external) or a location (in-workbook).");
|
|
3964
|
+
}
|
|
3965
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3966
|
+
const normalized = normalizeRangeRef(ref);
|
|
3967
|
+
__privateGet(this, _hyperlinks).set(normalized, { ref: normalized, ...link });
|
|
3968
|
+
}
|
|
3969
|
+
/** The hyperlink covering exactly this ref, or undefined. */
|
|
3970
|
+
getHyperlink(ref) {
|
|
3971
|
+
return __privateGet(this, _hyperlinks).get(normalizeRangeRef(ref));
|
|
3972
|
+
}
|
|
3973
|
+
/** Removes the hyperlink on this ref. Returns true if one was there. */
|
|
3974
|
+
removeHyperlink(ref) {
|
|
3975
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3976
|
+
return __privateGet(this, _hyperlinks).delete(normalizeRangeRef(ref));
|
|
3977
|
+
}
|
|
3978
|
+
/** Every hyperlink on the sheet, including ones read from a file. */
|
|
3979
|
+
get hyperlinks() {
|
|
3980
|
+
return [...__privateGet(this, _hyperlinks).values()];
|
|
3981
|
+
}
|
|
3982
|
+
// ── Comments (notes) ───────────────────────────────────────────────────────
|
|
3983
|
+
/**
|
|
3984
|
+
* Attaches a comment (a "note" in current Excel versions) to a cell,
|
|
3985
|
+
* replacing any comment already there.
|
|
3986
|
+
*
|
|
3987
|
+
* Note that writing comments makes this sheet regenerate its comment and
|
|
3988
|
+
* legacy-drawing parts on save. If the file was opened with other legacy
|
|
3989
|
+
* drawing content on the same sheet — form controls, for instance — that
|
|
3990
|
+
* content is not reproduced. Reading comments has no such effect.
|
|
3991
|
+
*
|
|
3992
|
+
* @example
|
|
3993
|
+
* ws.setComment('B4', 'Check this figure', { author: 'Bill' });
|
|
3994
|
+
*/
|
|
3995
|
+
setComment(ref, text, options) {
|
|
3996
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
3997
|
+
const normalized = normalizeRef(ref);
|
|
3998
|
+
__privateGet(this, _comments).set(normalized, {
|
|
3999
|
+
ref: normalized,
|
|
4000
|
+
text,
|
|
4001
|
+
author: options?.author ?? ""
|
|
4002
|
+
});
|
|
4003
|
+
__privateSet(this, _commentsDirty, true);
|
|
4004
|
+
}
|
|
4005
|
+
/** The comment on a cell, or undefined. */
|
|
4006
|
+
getComment(ref) {
|
|
4007
|
+
return __privateGet(this, _comments).get(normalizeRef(ref));
|
|
4008
|
+
}
|
|
4009
|
+
/** Removes the comment on a cell. Returns true if one was there. */
|
|
4010
|
+
removeComment(ref) {
|
|
4011
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
4012
|
+
const removed = __privateGet(this, _comments).delete(normalizeRef(ref));
|
|
4013
|
+
if (removed) __privateSet(this, _commentsDirty, true);
|
|
4014
|
+
return removed;
|
|
4015
|
+
}
|
|
4016
|
+
/** Every comment on the sheet, including ones read from a file. */
|
|
4017
|
+
get comments() {
|
|
4018
|
+
return [...__privateGet(this, _comments).values()];
|
|
4019
|
+
}
|
|
4020
|
+
/** @internal True when this sheet must write its own comment parts. */
|
|
4021
|
+
get ownsCommentParts() {
|
|
4022
|
+
return __privateGet(this, _commentsDirty) && __privateGet(this, _comments).size > 0;
|
|
4023
|
+
}
|
|
4024
|
+
/**
|
|
4025
|
+
* @internal True when the sheet took over its comment parts and any preserved
|
|
4026
|
+
* originals must be dropped — including the case where every comment was
|
|
4027
|
+
* deleted and nothing replaces them.
|
|
4028
|
+
*/
|
|
4029
|
+
get commentPartsReplaced() {
|
|
4030
|
+
return __privateGet(this, _commentsDirty);
|
|
4031
|
+
}
|
|
2520
4032
|
// ── Drawings (charts, images …) ──────────────────────────────────────────────
|
|
2521
4033
|
/**
|
|
2522
4034
|
* Attaches a drawing provider (e.g. a chart) to this worksheet.
|
|
@@ -2524,6 +4036,7 @@ var Worksheet = class {
|
|
|
2524
4036
|
* save pipeline turns registered providers into valid OOXML drawing/chart parts.
|
|
2525
4037
|
*/
|
|
2526
4038
|
addDrawingProvider(provider) {
|
|
4039
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2527
4040
|
__privateGet(this, _drawings).push(provider);
|
|
2528
4041
|
}
|
|
2529
4042
|
/**
|
|
@@ -2535,6 +4048,7 @@ var Worksheet = class {
|
|
|
2535
4048
|
* deliberately drop a chart that came from the source file.
|
|
2536
4049
|
*/
|
|
2537
4050
|
clearDrawings() {
|
|
4051
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2538
4052
|
__privateSet(this, _drawings, []);
|
|
2539
4053
|
__privateSet(this, _preservedTail, __privateGet(this, _preservedTail).filter((t) => t.tag !== "drawing"));
|
|
2540
4054
|
}
|
|
@@ -2554,7 +4068,12 @@ var Worksheet = class {
|
|
|
2554
4068
|
*/
|
|
2555
4069
|
get preservedRelationships() {
|
|
2556
4070
|
const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
|
|
2557
|
-
return __privateGet(this, _preservedRels).filter((r) =>
|
|
4071
|
+
return __privateGet(this, _preservedRels).filter((r) => {
|
|
4072
|
+
if (r.type === REL_TYPES.comments) {
|
|
4073
|
+
return !this.commentPartsReplaced && __privateGet(this, _comments).size > 0;
|
|
4074
|
+
}
|
|
4075
|
+
return liveIds.has(r.id);
|
|
4076
|
+
});
|
|
2558
4077
|
}
|
|
2559
4078
|
/**
|
|
2560
4079
|
* @internal Package paths of relationships that were preserved at load time
|
|
@@ -2571,55 +4090,119 @@ var Worksheet = class {
|
|
|
2571
4090
|
* clear every preserved id so the two sets cannot collide.
|
|
2572
4091
|
*/
|
|
2573
4092
|
get drawingRelId() {
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
4093
|
+
return this.generatedRels().drawing ?? `rId${__privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this) + 1}`;
|
|
4094
|
+
}
|
|
4095
|
+
/**
|
|
4096
|
+
* @internal Relationship ids for the parts this sheet generates itself.
|
|
4097
|
+
*
|
|
4098
|
+
* Derived purely from the model so the worksheet XML and the sheet's rels part
|
|
4099
|
+
* — which are written by different code — cannot disagree about an id. Order is
|
|
4100
|
+
* fixed: drawing, then external hyperlinks in insertion order, then the
|
|
4101
|
+
* comments part and its legacy VML drawing.
|
|
4102
|
+
*/
|
|
4103
|
+
generatedRels() {
|
|
4104
|
+
let next = __privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this);
|
|
4105
|
+
const result = { hyperlinks: /* @__PURE__ */ new Map() };
|
|
4106
|
+
if (this.hasDrawings) result.drawing = `rId${++next}`;
|
|
4107
|
+
for (const link of __privateGet(this, _hyperlinks).values()) {
|
|
4108
|
+
if (link.target) result.hyperlinks.set(link.ref, `rId${++next}`);
|
|
2578
4109
|
}
|
|
2579
|
-
|
|
4110
|
+
if (this.ownsCommentParts) {
|
|
4111
|
+
result.comments = `rId${++next}`;
|
|
4112
|
+
result.vmlDrawing = `rId${++next}`;
|
|
4113
|
+
}
|
|
4114
|
+
return result;
|
|
2580
4115
|
}
|
|
2581
4116
|
/** @internal Captures round-trip state from the source package. */
|
|
2582
4117
|
loadPreservedState(worksheetXml, relationships) {
|
|
2583
|
-
|
|
2584
|
-
|
|
4118
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
4119
|
+
__privateSet(this, _preservedRels, relationships.filter((r) => r.type !== REL_TYPES.hyperlink));
|
|
4120
|
+
const known = new Set(__privateGet(this, _preservedRels).map((r) => r.id));
|
|
2585
4121
|
__privateSet(this, _preservedTail, parseTailElements(worksheetXml).filter((t) => known.has(t.relId)));
|
|
4122
|
+
const byId = new Map(relationships.map((r) => [r.id, r]));
|
|
4123
|
+
for (const [ref, relId] of __privateGet(this, _pendingHyperlinkRels)) {
|
|
4124
|
+
const rel = byId.get(relId);
|
|
4125
|
+
const link = __privateGet(this, _hyperlinks).get(ref);
|
|
4126
|
+
if (!rel || !link) continue;
|
|
4127
|
+
__privateGet(this, _hyperlinks).set(ref, { ...link, target: rel.target });
|
|
4128
|
+
}
|
|
4129
|
+
__privateGet(this, _pendingHyperlinkRels).clear();
|
|
4130
|
+
for (const [ref, link] of [...__privateGet(this, _hyperlinks)]) {
|
|
4131
|
+
if (!link.target && !link.location) __privateGet(this, _hyperlinks).delete(ref);
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
/**
|
|
4135
|
+
* @internal Package paths of the comment parts this sheet read at load time.
|
|
4136
|
+
* The save pipeline drops them from the preserved set once the sheet takes
|
|
4137
|
+
* ownership, so they are not written twice.
|
|
4138
|
+
*/
|
|
4139
|
+
commentPartPaths() {
|
|
4140
|
+
const paths = [];
|
|
4141
|
+
for (const rel of __privateGet(this, _preservedRels)) {
|
|
4142
|
+
if (!rel.resolved) continue;
|
|
4143
|
+
if (rel.type === REL_TYPES.comments || rel.type === REL_TYPES.vmlDrawing) {
|
|
4144
|
+
paths.push(rel.resolved);
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
return paths;
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* @internal The relationship pointing at this sheet's comments part, if the
|
|
4151
|
+
* sheet was loaded from a file that had one.
|
|
4152
|
+
*/
|
|
4153
|
+
commentsPartPath() {
|
|
4154
|
+
return __privateGet(this, _preservedRels).find((r) => r.type === REL_TYPES.comments)?.resolved;
|
|
2586
4155
|
}
|
|
2587
4156
|
// ── XML serialisation ──────────────────────────────────────────────────────
|
|
2588
4157
|
/** Builds the xl/worksheets/sheetN.xml string. */
|
|
2589
4158
|
buildXml() {
|
|
4159
|
+
const memo = __privateGet(this, _xmlMemo);
|
|
4160
|
+
if (memo && memo.at === __privateGet(this, _version2).v) return memo.xml;
|
|
4161
|
+
const rels = this.generatedRels();
|
|
4162
|
+
const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
|
|
2590
4163
|
const colsXml = __privateMethod(this, _Worksheet_instances, buildColsXml_fn).call(this);
|
|
2591
4164
|
const sheetDataXml = __privateMethod(this, _Worksheet_instances, buildSheetDataXml_fn).call(this);
|
|
2592
|
-
const
|
|
4165
|
+
const protectionXml = __privateMethod(this, _Worksheet_instances, buildSheetProtectionXml_fn).call(this);
|
|
2593
4166
|
const filterXml = __privateGet(this, _autoFilter) ? `<autoFilter ref="${__privateGet(this, _autoFilter).ref}"/>` : "";
|
|
4167
|
+
const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
|
|
2594
4168
|
const validXml = __privateMethod(this, _Worksheet_instances, buildDataValidationsXml_fn).call(this);
|
|
2595
|
-
const
|
|
2596
|
-
const
|
|
2597
|
-
const
|
|
2598
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
4169
|
+
const linksXml = __privateMethod(this, _Worksheet_instances, buildHyperlinksXml_fn).call(this, rels.hyperlinks);
|
|
4170
|
+
const tailXml = __privateMethod(this, _Worksheet_instances, buildTailXml_fn).call(this, rels);
|
|
4171
|
+
const xml2 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2599
4172
|
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
2600
4173
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
2601
4174
|
<sheetViews>${sheetViewXml}</sheetViews>
|
|
2602
|
-
${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${
|
|
4175
|
+
${colsXml}${sheetDataXml}${protectionXml}${filterXml}${mergeXml}${validXml}${linksXml}${tailXml}
|
|
2603
4176
|
</worksheet>`;
|
|
4177
|
+
__privateSet(this, _xmlMemo, { at: __privateGet(this, _version2).v, xml: xml2 });
|
|
4178
|
+
return xml2;
|
|
2604
4179
|
}
|
|
2605
4180
|
/** Parses xl/worksheets/sheetN.xml and loads cells into memory. */
|
|
2606
4181
|
loadFromXml(xml2) {
|
|
4182
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
2607
4183
|
__privateGet(this, _rows).clear();
|
|
2608
4184
|
__privateSet(this, _cols, []);
|
|
2609
4185
|
__privateSet(this, _mergeCells, []);
|
|
2610
4186
|
__privateSet(this, _pane, void 0);
|
|
2611
4187
|
__privateSet(this, _autoFilter, void 0);
|
|
2612
4188
|
__privateSet(this, _dataValidations, []);
|
|
4189
|
+
__privateSet(this, _protection, void 0);
|
|
4190
|
+
__privateSet(this, _protectionCredentials, "");
|
|
4191
|
+
__privateGet(this, _hyperlinks).clear();
|
|
4192
|
+
__privateGet(this, _pendingHyperlinkRels).clear();
|
|
2613
4193
|
const root = parseXml(xml2);
|
|
2614
4194
|
for (const colEl of getElementsByTagName(root, "col")) {
|
|
4195
|
+
const widthAttr = getAttr(colEl, "width");
|
|
2615
4196
|
__privateGet(this, _cols).push({
|
|
2616
4197
|
min: parseInt(getAttr(colEl, "min") ?? "1", 10),
|
|
2617
4198
|
max: parseInt(getAttr(colEl, "max") ?? "1", 10),
|
|
2618
|
-
width: parseFloat(
|
|
4199
|
+
width: widthAttr !== void 0 ? parseFloat(widthAttr) : void 0,
|
|
2619
4200
|
customWidth: getAttr(colEl, "customWidth") === "1",
|
|
2620
4201
|
hidden: getAttr(colEl, "hidden") === "1"
|
|
2621
4202
|
});
|
|
2622
4203
|
}
|
|
4204
|
+
const sharedMasters = /* @__PURE__ */ new Map();
|
|
4205
|
+
const sharedInstances = [];
|
|
2623
4206
|
for (const rowEl of getElementsByTagName(root, "row")) {
|
|
2624
4207
|
const rowIndex = parseInt(getAttr(rowEl, "r") ?? "0", 10);
|
|
2625
4208
|
if (rowIndex < 1) continue;
|
|
@@ -2640,9 +4223,17 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${
|
|
|
2640
4223
|
ref.toUpperCase()
|
|
2641
4224
|
);
|
|
2642
4225
|
rowData.cells.set(ref.toUpperCase(), cellData);
|
|
4226
|
+
if (cellData.sharedIndex !== void 0) {
|
|
4227
|
+
if (cellData.formula !== void 0) {
|
|
4228
|
+
sharedMasters.set(cellData.sharedIndex, { formula: cellData.formula, ref: cellData.reference });
|
|
4229
|
+
} else {
|
|
4230
|
+
sharedInstances.push(cellData);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
2643
4233
|
}
|
|
2644
4234
|
__privateGet(this, _rows).set(rowIndex, rowData);
|
|
2645
4235
|
}
|
|
4236
|
+
__privateMethod(this, _Worksheet_instances, resolveSharedFormulas_fn).call(this, sharedMasters, sharedInstances);
|
|
2646
4237
|
for (const mcEl of getElementsByTagName(root, "mergeCell")) {
|
|
2647
4238
|
const ref = getAttr(mcEl, "ref");
|
|
2648
4239
|
if (ref) __privateGet(this, _mergeCells).push({ ref });
|
|
@@ -2667,6 +4258,76 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${
|
|
|
2667
4258
|
const formula = f1Els[0]?.textContent ?? "";
|
|
2668
4259
|
if (sqref && formula) __privateGet(this, _dataValidations).push({ sqref, formula });
|
|
2669
4260
|
}
|
|
4261
|
+
const protEl = getElementsByTagName(root, "sheetProtection")[0];
|
|
4262
|
+
if (protEl && getAttr(protEl, "sheet") === "1") {
|
|
4263
|
+
const protection = { ...PROTECTION_DEFAULTS };
|
|
4264
|
+
for (const [key, attr] of PROTECTION_ATTRS) {
|
|
4265
|
+
const raw = getAttr(protEl, attr);
|
|
4266
|
+
if (raw !== void 0) protection[key] = raw !== "1";
|
|
4267
|
+
}
|
|
4268
|
+
__privateSet(this, _protection, protection);
|
|
4269
|
+
let credentials = "";
|
|
4270
|
+
for (const attr of ["password", "algorithmName", "hashValue", "saltValue", "spinCount"]) {
|
|
4271
|
+
const raw = getAttr(protEl, attr);
|
|
4272
|
+
if (raw !== void 0) credentials += ` ${attr}="${escXmlAttr(raw)}"`;
|
|
4273
|
+
}
|
|
4274
|
+
__privateSet(this, _protectionCredentials, credentials);
|
|
4275
|
+
}
|
|
4276
|
+
for (const hlEl of getElementsByTagName(root, "hyperlink")) {
|
|
4277
|
+
const ref = getAttr(hlEl, "ref");
|
|
4278
|
+
if (!ref) continue;
|
|
4279
|
+
const normalized = normalizeRangeRef(ref);
|
|
4280
|
+
__privateGet(this, _hyperlinks).set(normalized, {
|
|
4281
|
+
ref: normalized,
|
|
4282
|
+
location: getAttr(hlEl, "location"),
|
|
4283
|
+
display: getAttr(hlEl, "display"),
|
|
4284
|
+
tooltip: getAttr(hlEl, "tooltip")
|
|
4285
|
+
});
|
|
4286
|
+
const relId = getAttr(hlEl, "r:id") ?? getAttr(hlEl, "id");
|
|
4287
|
+
if (relId) __privateGet(this, _pendingHyperlinkRels).set(normalized, relId);
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
/** @internal Parses an `xl/comments*.xml` part into this sheet's comments. */
|
|
4291
|
+
loadCommentsXml(xml2) {
|
|
4292
|
+
__privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
|
|
4293
|
+
const root = parseXml(xml2);
|
|
4294
|
+
const authors = getElementsByTagName(root, "author").map((a) => a.textContent);
|
|
4295
|
+
for (const cEl of getElementsByTagName(root, "comment")) {
|
|
4296
|
+
const ref = getAttr(cEl, "ref");
|
|
4297
|
+
if (!ref) continue;
|
|
4298
|
+
const authorId = parseInt(getAttr(cEl, "authorId") ?? "0", 10);
|
|
4299
|
+
const text = getElementsByTagName(cEl, "t").map((t) => t.textContent).join("");
|
|
4300
|
+
const normalized = normalizeRef(ref);
|
|
4301
|
+
__privateGet(this, _comments).set(normalized, {
|
|
4302
|
+
ref: normalized,
|
|
4303
|
+
text,
|
|
4304
|
+
author: authors[authorId] ?? ""
|
|
4305
|
+
});
|
|
4306
|
+
}
|
|
4307
|
+
__privateSet(this, _commentsDirty, false);
|
|
4308
|
+
}
|
|
4309
|
+
/** @internal The comments part content, when this sheet owns it. */
|
|
4310
|
+
buildCommentsXml() {
|
|
4311
|
+
const authors = [...new Set([...__privateGet(this, _comments).values()].map((c) => c.author))];
|
|
4312
|
+
const authorsXml = authors.map((a) => `<author>${escXml2(a)}</author>`).join("");
|
|
4313
|
+
const listXml = [...__privateGet(this, _comments).values()].map(
|
|
4314
|
+
(c) => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.author)}"><text><r><t xml:space="preserve">${escXml2(c.text)}</t></r></text></comment>`
|
|
4315
|
+
).join("");
|
|
4316
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
4317
|
+
<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authorsXml}</authors><commentList>${listXml}</commentList></comments>`;
|
|
4318
|
+
}
|
|
4319
|
+
/**
|
|
4320
|
+
* @internal The legacy VML drawing that positions the comment boxes. Excel
|
|
4321
|
+
* needs it to draw the note indicator and popup; the comments part alone
|
|
4322
|
+
* carries only the text.
|
|
4323
|
+
*/
|
|
4324
|
+
buildCommentsVml() {
|
|
4325
|
+
const shapes = [...__privateGet(this, _comments).values()].map((c, idx) => {
|
|
4326
|
+
const { row, column } = parseCellRef(c.ref);
|
|
4327
|
+
const anchor = `${column}, 15, ${row - 1}, 10, ${column + 2}, 15, ${row + 3}, 4`;
|
|
4328
|
+
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>`;
|
|
4329
|
+
}).join("");
|
|
4330
|
+
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>`;
|
|
2670
4331
|
}
|
|
2671
4332
|
};
|
|
2672
4333
|
_sharedStrings2 = new WeakMap();
|
|
@@ -2680,23 +4341,331 @@ _autoFilter = new WeakMap();
|
|
|
2680
4341
|
_dataValidations = new WeakMap();
|
|
2681
4342
|
_visibility = new WeakMap();
|
|
2682
4343
|
_drawings = new WeakMap();
|
|
4344
|
+
_protection = new WeakMap();
|
|
4345
|
+
_protectionCredentials = new WeakMap();
|
|
4346
|
+
_hyperlinks = new WeakMap();
|
|
4347
|
+
_comments = new WeakMap();
|
|
4348
|
+
_commentsDirty = new WeakMap();
|
|
4349
|
+
_pendingHyperlinkRels = new WeakMap();
|
|
2683
4350
|
_preservedTail = new WeakMap();
|
|
2684
4351
|
_preservedRels = new WeakMap();
|
|
4352
|
+
_version2 = new WeakMap();
|
|
4353
|
+
_usedBoundsMemo = new WeakMap();
|
|
4354
|
+
_xmlMemo = new WeakMap();
|
|
2685
4355
|
_Worksheet_instances = new WeakSet();
|
|
4356
|
+
/** Records a change to this sheet's contents or layout. */
|
|
4357
|
+
touch_fn2 = function() {
|
|
4358
|
+
__privateGet(this, _version2).v++;
|
|
4359
|
+
};
|
|
4360
|
+
/**
|
|
4361
|
+
* The stored record for a cell, without creating it — the read paths below
|
|
4362
|
+
* work on this directly instead of wrapping it in a {@link Cell}. Formula
|
|
4363
|
+
* evaluation reads far more cells than it writes, and one wrapper per read is
|
|
4364
|
+
* pure garbage.
|
|
4365
|
+
*/
|
|
4366
|
+
findCellData_fn = function(reference) {
|
|
4367
|
+
const ref = normalizeRefFast(reference);
|
|
4368
|
+
return __privateGet(this, _rows).get(parseCellRef(ref).row)?.cells.get(ref);
|
|
4369
|
+
};
|
|
4370
|
+
/**
|
|
4371
|
+
* The extent, computed without touching the memo.
|
|
4372
|
+
*
|
|
4373
|
+
* Internal callers must use this. A mutator bumps the version once, at its
|
|
4374
|
+
* start, and then does its work; if it read the memoised getter part-way
|
|
4375
|
+
* through, the half-finished extent would be cached against the *new* version
|
|
4376
|
+
* and served as current for the rest of that version's life. That is precisely
|
|
4377
|
+
* how `insertColumns` came to report the extent it had before the insert —
|
|
4378
|
+
* caught by the round-trip fuzz test, not by any of the hand-written ones.
|
|
4379
|
+
*/
|
|
4380
|
+
computeUsedBounds_fn = function() {
|
|
4381
|
+
let rowCount = 0;
|
|
4382
|
+
let columnCount = 0;
|
|
4383
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
4384
|
+
if (row.cells.size === 0) continue;
|
|
4385
|
+
if (row.rowIndex > rowCount) rowCount = row.rowIndex;
|
|
4386
|
+
for (const ref of row.cells.keys()) {
|
|
4387
|
+
const col = columnFromRef(ref, refSplit(ref));
|
|
4388
|
+
if (col > columnCount) columnCount = col;
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
return { rowCount, columnCount };
|
|
4392
|
+
};
|
|
4393
|
+
/** Removes and returns a cell's data record, pruning an emptied bare row. */
|
|
4394
|
+
takeCellData_fn = function(ref) {
|
|
4395
|
+
return __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, parseCellRef(ref).row, ref);
|
|
4396
|
+
};
|
|
4397
|
+
takeCellDataAt_fn = function(row, ref) {
|
|
4398
|
+
const rowData = __privateGet(this, _rows).get(row);
|
|
4399
|
+
const data = rowData?.cells.get(ref);
|
|
4400
|
+
if (rowData && data) {
|
|
4401
|
+
rowData.cells.delete(ref);
|
|
4402
|
+
if (rowData.cells.size === 0 && !rowData.hidden && rowData.height === void 0) {
|
|
4403
|
+
__privateGet(this, _rows).delete(row);
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
return data;
|
|
4407
|
+
};
|
|
4408
|
+
deleteCellData_fn = function(ref) {
|
|
4409
|
+
__privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, ref);
|
|
4410
|
+
};
|
|
4411
|
+
applyStructural_fn = function(dim, kind, at, count) {
|
|
4412
|
+
const what = dim === "row" ? "Row" : "Column";
|
|
4413
|
+
if (!Number.isInteger(at) || at < 1) throw new RangeError(`${what} index must be an integer >= 1.`);
|
|
4414
|
+
if (!Number.isInteger(count) || count < 1) throw new RangeError("Count must be an integer >= 1.");
|
|
4415
|
+
const shift = { dim, kind, at, count, sheetName: __privateGet(this, _name) };
|
|
4416
|
+
if (kind === "insert") {
|
|
4417
|
+
const limit = dim === "row" ? EXCEL_MAX_ROWS : EXCEL_MAX_COLUMNS;
|
|
4418
|
+
const used = dim === "row" ? __privateMethod(this, _Worksheet_instances, maxPopulatedRow_fn).call(this) : __privateMethod(this, _Worksheet_instances, computeUsedBounds_fn).call(this).columnCount;
|
|
4419
|
+
if (used >= at && used + count > limit) {
|
|
4420
|
+
throw new RangeError(`Insert would push populated cells past the sheet limit of ${limit}.`);
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
__privateMethod(this, _Worksheet_instances, shiftRowsAndCells_fn).call(this, shift);
|
|
4424
|
+
const refErrors = __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift);
|
|
4425
|
+
__privateMethod(this, _Worksheet_instances, shiftMerges_fn).call(this, shift);
|
|
4426
|
+
__privateMethod(this, _Worksheet_instances, shiftAutoFilter_fn).call(this, shift);
|
|
4427
|
+
__privateMethod(this, _Worksheet_instances, shiftValidations_fn).call(this, shift);
|
|
4428
|
+
__privateMethod(this, _Worksheet_instances, shiftHyperlinks_fn).call(this, shift);
|
|
4429
|
+
__privateMethod(this, _Worksheet_instances, shiftComments_fn).call(this, shift);
|
|
4430
|
+
if (dim === "col") __privateMethod(this, _Worksheet_instances, shiftColDefs_fn).call(this, shift);
|
|
4431
|
+
return { refErrors };
|
|
4432
|
+
};
|
|
4433
|
+
/**
|
|
4434
|
+
* Re-keys the row map / per-row cell maps and shifts every cell's spill range.
|
|
4435
|
+
*
|
|
4436
|
+
* Rows and columns before the shift boundary keep their keys, their reference
|
|
4437
|
+
* strings and their identity, so an edit near the end of a sheet costs
|
|
4438
|
+
* proportionally little rather than rebuilding the whole model. Spill ranges
|
|
4439
|
+
* are the one thing that must be visited everywhere, since a range belonging
|
|
4440
|
+
* to an unmoved anchor can still span the boundary — but that pass is a bare
|
|
4441
|
+
* property test per cell.
|
|
4442
|
+
*/
|
|
4443
|
+
shiftRowsAndCells_fn = function(shift) {
|
|
4444
|
+
if (shift.dim === "row") __privateMethod(this, _Worksheet_instances, shiftRows_fn).call(this, shift);
|
|
4445
|
+
else __privateMethod(this, _Worksheet_instances, shiftColumns_fn).call(this, shift);
|
|
4446
|
+
};
|
|
4447
|
+
shiftRows_fn = function(shift) {
|
|
4448
|
+
const moved = [];
|
|
4449
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
4450
|
+
if (row.rowIndex < shift.at) {
|
|
4451
|
+
__privateMethod(this, _Worksheet_instances, shiftSpillRanges_fn).call(this, row, shift);
|
|
4452
|
+
continue;
|
|
4453
|
+
}
|
|
4454
|
+
const shifted = shiftIndex(row.rowIndex, shift);
|
|
4455
|
+
__privateGet(this, _rows).delete(row.rowIndex);
|
|
4456
|
+
if (shifted === null) continue;
|
|
4457
|
+
row.rowIndex = shifted;
|
|
4458
|
+
moved.push(row);
|
|
4459
|
+
}
|
|
4460
|
+
for (const row of moved) {
|
|
4461
|
+
const suffix = String(row.rowIndex);
|
|
4462
|
+
const newCells = /* @__PURE__ */ new Map();
|
|
4463
|
+
for (const cd of row.cells.values()) {
|
|
4464
|
+
const ref = cd.reference.slice(0, refSplit(cd.reference)) + suffix;
|
|
4465
|
+
cd.reference = ref;
|
|
4466
|
+
if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
|
|
4467
|
+
newCells.set(ref, cd);
|
|
4468
|
+
}
|
|
4469
|
+
row.cells = newCells;
|
|
4470
|
+
__privateGet(this, _rows).set(row.rowIndex, row);
|
|
4471
|
+
}
|
|
4472
|
+
};
|
|
4473
|
+
shiftColumns_fn = function(shift) {
|
|
4474
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
4475
|
+
let affected;
|
|
4476
|
+
for (const cd of row.cells.values()) {
|
|
4477
|
+
const ref = cd.reference;
|
|
4478
|
+
if (columnFromRef(ref, refSplit(ref)) < shift.at) {
|
|
4479
|
+
__privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
|
|
4480
|
+
continue;
|
|
4481
|
+
}
|
|
4482
|
+
(affected ?? (affected = [])).push(cd);
|
|
4483
|
+
}
|
|
4484
|
+
if (!affected) continue;
|
|
4485
|
+
for (const cd of affected) row.cells.delete(cd.reference);
|
|
4486
|
+
for (const cd of affected) {
|
|
4487
|
+
const split = refSplit(cd.reference);
|
|
4488
|
+
const shifted = shiftIndex(columnFromRef(cd.reference, split), shift);
|
|
4489
|
+
if (shifted === null) continue;
|
|
4490
|
+
const ref = columnLetter(shifted) + cd.reference.slice(split);
|
|
4491
|
+
cd.reference = ref;
|
|
4492
|
+
__privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
|
|
4493
|
+
row.cells.set(ref, cd);
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
};
|
|
4497
|
+
/** Largest 1-based row index holding a cell, or 0. O(rows), no ref parsing. */
|
|
4498
|
+
maxPopulatedRow_fn = function() {
|
|
4499
|
+
let max = 0;
|
|
4500
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
4501
|
+
if (row.cells.size > 0 && row.rowIndex > max) max = row.rowIndex;
|
|
4502
|
+
}
|
|
4503
|
+
return max;
|
|
4504
|
+
};
|
|
4505
|
+
shiftSpillRanges_fn = function(row, shift) {
|
|
4506
|
+
for (const cd of row.cells.values()) __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
|
|
4507
|
+
};
|
|
4508
|
+
shiftSpillRange_fn = function(cd, shift) {
|
|
4509
|
+
if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
|
|
4510
|
+
};
|
|
4511
|
+
/** Rewrites every formula on the sheet; returns refs that now hold #REF!. */
|
|
4512
|
+
rewriteFormulas_fn = function(shift) {
|
|
4513
|
+
const refErrors = [];
|
|
4514
|
+
for (const row of __privateGet(this, _rows).values()) {
|
|
4515
|
+
for (const cd of row.cells.values()) {
|
|
4516
|
+
if (!cd.formula) continue;
|
|
4517
|
+
const result = shiftFormulaRefs(cd.formula, shift, __privateGet(this, _name));
|
|
4518
|
+
if (result.changed) cd.formula = result.text;
|
|
4519
|
+
if (result.hasRefError) refErrors.push(cd.reference);
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
return refErrors;
|
|
4523
|
+
};
|
|
4524
|
+
shiftMerges_fn = function(shift) {
|
|
4525
|
+
__privateSet(this, _mergeCells, __privateGet(this, _mergeCells).flatMap((m) => {
|
|
4526
|
+
const ref = shiftRangeRef(m.ref, shift);
|
|
4527
|
+
if (!ref) return [];
|
|
4528
|
+
const [a, b] = ref.split(":");
|
|
4529
|
+
if (!b || a === b) return [];
|
|
4530
|
+
return [{ ref }];
|
|
4531
|
+
}));
|
|
4532
|
+
};
|
|
4533
|
+
shiftAutoFilter_fn = function(shift) {
|
|
4534
|
+
if (!__privateGet(this, _autoFilter)) return;
|
|
4535
|
+
const ref = shiftRangeRef(__privateGet(this, _autoFilter).ref, shift);
|
|
4536
|
+
__privateSet(this, _autoFilter, ref ? { ref } : void 0);
|
|
4537
|
+
};
|
|
4538
|
+
shiftValidations_fn = function(shift) {
|
|
4539
|
+
__privateSet(this, _dataValidations, __privateGet(this, _dataValidations).flatMap((dv) => {
|
|
4540
|
+
const sqref = shiftSqref(dv.sqref, shift);
|
|
4541
|
+
if (!sqref) return [];
|
|
4542
|
+
const formula = shiftFormulaRefs(dv.formula, shift, __privateGet(this, _name)).text;
|
|
4543
|
+
return [{ sqref, formula }];
|
|
4544
|
+
}));
|
|
4545
|
+
};
|
|
4546
|
+
shiftHyperlinks_fn = function(shift) {
|
|
4547
|
+
const moved = /* @__PURE__ */ new Map();
|
|
4548
|
+
for (const link of __privateGet(this, _hyperlinks).values()) {
|
|
4549
|
+
const ref = shiftRangeRef(link.ref, shift);
|
|
4550
|
+
if (!ref) continue;
|
|
4551
|
+
moved.set(ref, { ...link, ref });
|
|
4552
|
+
}
|
|
4553
|
+
__privateSet(this, _hyperlinks, moved);
|
|
4554
|
+
};
|
|
4555
|
+
shiftComments_fn = function(shift) {
|
|
4556
|
+
const moved = /* @__PURE__ */ new Map();
|
|
4557
|
+
let changed = false;
|
|
4558
|
+
for (const comment of __privateGet(this, _comments).values()) {
|
|
4559
|
+
const ref = shiftRangeRef(comment.ref, shift);
|
|
4560
|
+
if (!ref) {
|
|
4561
|
+
changed = true;
|
|
4562
|
+
continue;
|
|
4563
|
+
}
|
|
4564
|
+
if (ref !== comment.ref) changed = true;
|
|
4565
|
+
moved.set(ref, { ...comment, ref });
|
|
4566
|
+
}
|
|
4567
|
+
__privateSet(this, _comments, moved);
|
|
4568
|
+
if (changed) __privateSet(this, _commentsDirty, true);
|
|
4569
|
+
};
|
|
4570
|
+
shiftColDefs_fn = function(shift) {
|
|
4571
|
+
__privateSet(this, _cols, __privateGet(this, _cols).flatMap((c) => {
|
|
4572
|
+
const span = shiftSpan(c.min, c.max, shift);
|
|
4573
|
+
return span ? [{ ...c, min: span.start, max: span.end }] : [];
|
|
4574
|
+
}));
|
|
4575
|
+
};
|
|
4576
|
+
/** Highest numeric preserved relationship id, so generated ids clear them all. */
|
|
4577
|
+
relIdBase_fn = function() {
|
|
4578
|
+
let max = 0;
|
|
4579
|
+
for (const r of __privateGet(this, _preservedRels)) {
|
|
4580
|
+
const n = RID_PATTERN.exec(r.id);
|
|
4581
|
+
if (n) max = Math.max(max, parseInt(n[1], 10));
|
|
4582
|
+
}
|
|
4583
|
+
return max;
|
|
4584
|
+
};
|
|
2686
4585
|
/**
|
|
2687
4586
|
* Preserved leaf elements that will actually be emitted. A sheet may only have
|
|
2688
4587
|
* one `<drawing>`, so live providers suppress a preserved one — every consumer
|
|
2689
4588
|
* (serialisation, relationships, pruning) must agree on that, hence one helper.
|
|
2690
4589
|
*/
|
|
2691
4590
|
activeTail_fn = function() {
|
|
2692
|
-
return __privateGet(this, _preservedTail).filter((t) =>
|
|
4591
|
+
return __privateGet(this, _preservedTail).filter((t) => {
|
|
4592
|
+
if (t.tag === "drawing") return !this.hasDrawings;
|
|
4593
|
+
if (t.tag === "legacyDrawing") return !this.commentPartsReplaced;
|
|
4594
|
+
return true;
|
|
4595
|
+
});
|
|
4596
|
+
};
|
|
4597
|
+
/**
|
|
4598
|
+
* The relationship-bearing leaf elements, in schema order: `drawing`,
|
|
4599
|
+
* `legacyDrawing`, `legacyDrawingHF`, `picture`. A sheet may hold only one of
|
|
4600
|
+
* each, so generated content replaces the preserved element of the same tag —
|
|
4601
|
+
* live drawing providers replace a preserved `<drawing>`, and comments this
|
|
4602
|
+
* sheet now owns replace a preserved `<legacyDrawing>`.
|
|
4603
|
+
*/
|
|
4604
|
+
buildTailXml_fn = function(rels) {
|
|
4605
|
+
const preserved = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this);
|
|
4606
|
+
const byTag = (tag) => preserved.filter((t) => t.tag === tag).map((t) => t.xml).join("");
|
|
4607
|
+
const drawing = rels.drawing ? `<drawing r:id="${rels.drawing}"/>` : byTag("drawing");
|
|
4608
|
+
const legacy = rels.vmlDrawing ? `<legacyDrawing r:id="${rels.vmlDrawing}"/>` : byTag("legacyDrawing");
|
|
4609
|
+
return drawing + legacy + byTag("legacyDrawingHF") + byTag("picture");
|
|
4610
|
+
};
|
|
4611
|
+
buildSheetProtectionXml_fn = function() {
|
|
4612
|
+
if (!__privateGet(this, _protection)) return "";
|
|
4613
|
+
let attrs = ' sheet="1"';
|
|
4614
|
+
for (const [key, attr] of PROTECTION_ATTRS) {
|
|
4615
|
+
const blocked = !__privateGet(this, _protection)[key];
|
|
4616
|
+
const defaultBlocked = !PROTECTION_DEFAULTS[key];
|
|
4617
|
+
if (blocked !== defaultBlocked) attrs += ` ${attr}="${blocked ? 1 : 0}"`;
|
|
4618
|
+
}
|
|
4619
|
+
return `<sheetProtection${__privateGet(this, _protectionCredentials)}${attrs}/>`;
|
|
4620
|
+
};
|
|
4621
|
+
buildHyperlinksXml_fn = function(relIds) {
|
|
4622
|
+
if (__privateGet(this, _hyperlinks).size === 0) return "";
|
|
4623
|
+
const inner = [...__privateGet(this, _hyperlinks).values()].map((link) => {
|
|
4624
|
+
const relId = relIds.get(link.ref);
|
|
4625
|
+
const parts = [`ref="${link.ref}"`];
|
|
4626
|
+
if (relId) parts.push(`r:id="${relId}"`);
|
|
4627
|
+
if (link.location) parts.push(`location="${escXmlAttr(link.location)}"`);
|
|
4628
|
+
if (link.display) parts.push(`display="${escXmlAttr(link.display)}"`);
|
|
4629
|
+
if (link.tooltip) parts.push(`tooltip="${escXmlAttr(link.tooltip)}"`);
|
|
4630
|
+
return `<hyperlink ${parts.join(" ")}/>`;
|
|
4631
|
+
}).join("");
|
|
4632
|
+
return `<hyperlinks>${inner}</hyperlinks>`;
|
|
4633
|
+
};
|
|
4634
|
+
/**
|
|
4635
|
+
* Materialises the formula of every shared-formula instance cell.
|
|
4636
|
+
*
|
|
4637
|
+
* Excel writes a fill-down once: the group's master carries the text, the rest
|
|
4638
|
+
* carry `<f t="shared" si="n"/>` and are understood to hold the master's
|
|
4639
|
+
* formula with its relative references moved by the offset between the cells.
|
|
4640
|
+
* Read literally — the way this used to be read — an instance cell has no
|
|
4641
|
+
* formula at all, and the next save turns it into the constant that happened to
|
|
4642
|
+
* be cached in `<v>`. On a real workbook that is tens of thousands of formulas
|
|
4643
|
+
* quietly replaced by numbers.
|
|
4644
|
+
*
|
|
4645
|
+
* Each cell ends up owning its own text, so nothing downstream (evaluation,
|
|
4646
|
+
* structural edits, serialisation) needs to know the group ever existed.
|
|
4647
|
+
*/
|
|
4648
|
+
resolveSharedFormulas_fn = function(masters, instances) {
|
|
4649
|
+
if (instances.length === 0) return;
|
|
4650
|
+
for (const cd of instances) {
|
|
4651
|
+
const master = masters.get(cd.sharedIndex);
|
|
4652
|
+
if (!master) continue;
|
|
4653
|
+
const from = parseCellRef(master.ref);
|
|
4654
|
+
const to = parseCellRef(cd.reference);
|
|
4655
|
+
cd.formula = translateFormulaRefs(
|
|
4656
|
+
master.formula,
|
|
4657
|
+
to.row - from.row,
|
|
4658
|
+
to.column - from.column
|
|
4659
|
+
).text;
|
|
4660
|
+
}
|
|
2693
4661
|
};
|
|
2694
4662
|
// ── Private XML builders ───────────────────────────────────────────────────
|
|
2695
4663
|
buildColsXml_fn = function() {
|
|
2696
4664
|
if (__privateGet(this, _cols).length === 0) return "";
|
|
2697
4665
|
const inner = __privateGet(this, _cols).map((c) => {
|
|
4666
|
+
const width = c.width !== void 0 ? ` width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"` : "";
|
|
2698
4667
|
const hidden = c.hidden ? ' hidden="1"' : "";
|
|
2699
|
-
return `<col min="${c.min}" max="${c.max}"
|
|
4668
|
+
return `<col min="${c.min}" max="${c.max}"${width}${hidden}/>`;
|
|
2700
4669
|
}).join("");
|
|
2701
4670
|
return `<cols>${inner}</cols>`;
|
|
2702
4671
|
};
|
|
@@ -2706,12 +4675,8 @@ buildSheetDataXml_fn = function() {
|
|
|
2706
4675
|
const rowsXml = sortedRows.map((row) => {
|
|
2707
4676
|
const ht = row.height !== void 0 && row.customHeight ? ` ht="${row.height}" customHeight="1"` : "";
|
|
2708
4677
|
const hidden = row.hidden ? ' hidden="1"' : "";
|
|
2709
|
-
const sortedCells = [...row.cells.values()].sort((a, b) =>
|
|
2710
|
-
|
|
2711
|
-
const rb = parseCellRef(b.reference);
|
|
2712
|
-
return ra.column - rb.column;
|
|
2713
|
-
});
|
|
2714
|
-
const cellsXml = sortedCells.map((cd) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
|
|
4678
|
+
const sortedCells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col);
|
|
4679
|
+
const cellsXml = sortedCells.map(({ cd }) => buildCellXml(cd)).filter(Boolean).join("");
|
|
2715
4680
|
if (!cellsXml && !ht && !hidden) return "";
|
|
2716
4681
|
return `<row r="${row.rowIndex}"${ht}${hidden}>${cellsXml}</row>`;
|
|
2717
4682
|
}).filter(Boolean).join("");
|
|
@@ -2740,6 +4705,61 @@ buildSheetViewXml_fn = function() {
|
|
|
2740
4705
|
const ySplit = row > 0 ? ` ySplit="${row}"` : "";
|
|
2741
4706
|
return `<sheetView workbookViewId="0"><pane${xSplit}${ySplit} topLeftCell="${topLeft}" activePane="${activePane}" state="frozen"/></sheetView>`;
|
|
2742
4707
|
};
|
|
4708
|
+
/**
|
|
4709
|
+
* Returns the single-column ColDef covering `columnIndex`, splitting a wider
|
|
4710
|
+
* span into up to three pieces so the other columns keep their width/hidden
|
|
4711
|
+
* state. Returns undefined when no definition covers the column.
|
|
4712
|
+
*/
|
|
4713
|
+
splitColSpan_fn = function(columnIndex) {
|
|
4714
|
+
const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
|
|
4715
|
+
if (idx === -1) return void 0;
|
|
4716
|
+
const span = __privateGet(this, _cols)[idx];
|
|
4717
|
+
if (span.min === span.max) return span;
|
|
4718
|
+
const pieces = [];
|
|
4719
|
+
if (span.min < columnIndex) pieces.push({ ...span, max: columnIndex - 1 });
|
|
4720
|
+
const target = { ...span, min: columnIndex, max: columnIndex };
|
|
4721
|
+
pieces.push(target);
|
|
4722
|
+
if (span.max > columnIndex) pieces.push({ ...span, min: columnIndex + 1 });
|
|
4723
|
+
__privateGet(this, _cols).splice(idx, 1, ...pieces);
|
|
4724
|
+
return target;
|
|
4725
|
+
};
|
|
4726
|
+
/**
|
|
4727
|
+
* Inserts a definition keeping `#cols` sorted by `min`. Binary insertion,
|
|
4728
|
+
* because re-sorting the whole array per call makes setting widths for every
|
|
4729
|
+
* column of a wide sheet quadratic.
|
|
4730
|
+
*/
|
|
4731
|
+
insertColDef_fn = function(def) {
|
|
4732
|
+
let lo = 0;
|
|
4733
|
+
let hi = __privateGet(this, _cols).length;
|
|
4734
|
+
while (lo < hi) {
|
|
4735
|
+
const mid = lo + hi >>> 1;
|
|
4736
|
+
if (__privateGet(this, _cols)[mid].min < def.min) lo = mid + 1;
|
|
4737
|
+
else hi = mid;
|
|
4738
|
+
}
|
|
4739
|
+
__privateGet(this, _cols).splice(lo, 0, def);
|
|
4740
|
+
return def;
|
|
4741
|
+
};
|
|
4742
|
+
/** Index of the definition covering `columnIndex`, or -1. Binary search. */
|
|
4743
|
+
findColDefIndex_fn = function(columnIndex) {
|
|
4744
|
+
let lo = 0;
|
|
4745
|
+
let hi = __privateGet(this, _cols).length - 1;
|
|
4746
|
+
while (lo <= hi) {
|
|
4747
|
+
const mid = lo + hi >>> 1;
|
|
4748
|
+
const c = __privateGet(this, _cols)[mid];
|
|
4749
|
+
if (columnIndex < c.min) hi = mid - 1;
|
|
4750
|
+
else if (columnIndex > c.max) lo = mid + 1;
|
|
4751
|
+
else return mid;
|
|
4752
|
+
}
|
|
4753
|
+
return -1;
|
|
4754
|
+
};
|
|
4755
|
+
findColDef_fn = function(columnIndex) {
|
|
4756
|
+
const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
|
|
4757
|
+
return idx === -1 ? void 0 : __privateGet(this, _cols)[idx];
|
|
4758
|
+
};
|
|
4759
|
+
/** Drops definitions that no longer carry any information. */
|
|
4760
|
+
pruneColDefs_fn = function() {
|
|
4761
|
+
__privateSet(this, _cols, __privateGet(this, _cols).filter((c) => c.width !== void 0 || c.customWidth || c.hidden));
|
|
4762
|
+
};
|
|
2743
4763
|
getOrCreateRow_fn = function(rowIndex) {
|
|
2744
4764
|
let row = __privateGet(this, _rows).get(rowIndex);
|
|
2745
4765
|
if (!row) {
|
|
@@ -2748,9 +4768,64 @@ getOrCreateRow_fn = function(rowIndex) {
|
|
|
2748
4768
|
}
|
|
2749
4769
|
return row;
|
|
2750
4770
|
};
|
|
4771
|
+
var RID_PATTERN = /^rId(\d+)$/;
|
|
2751
4772
|
function escXml2(s) {
|
|
2752
4773
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2753
4774
|
}
|
|
4775
|
+
function escXmlAttr(s) {
|
|
4776
|
+
return escXml2(s).replace(/"/g, """);
|
|
4777
|
+
}
|
|
4778
|
+
function normalizeRef(ref) {
|
|
4779
|
+
const normalized = normalizeRefFast(ref);
|
|
4780
|
+
parseCellRef(normalized);
|
|
4781
|
+
return normalized;
|
|
4782
|
+
}
|
|
4783
|
+
function normalizeRangeRef(ref) {
|
|
4784
|
+
const normalized = normalizeRefFast(ref);
|
|
4785
|
+
const colon = normalized.indexOf(":");
|
|
4786
|
+
if (colon === -1) {
|
|
4787
|
+
parseCellRef(normalized);
|
|
4788
|
+
return normalized;
|
|
4789
|
+
}
|
|
4790
|
+
parseCellRef(normalized.slice(0, colon));
|
|
4791
|
+
parseCellRef(normalized.slice(colon + 1));
|
|
4792
|
+
return normalized;
|
|
4793
|
+
}
|
|
4794
|
+
function normalizeRefFast(ref) {
|
|
4795
|
+
return ref.indexOf("$") === -1 ? upperFast(ref) : ref.replace(/\$/g, "").toUpperCase();
|
|
4796
|
+
}
|
|
4797
|
+
function upperFast(s) {
|
|
4798
|
+
for (let i = 0; i < s.length; i++) {
|
|
4799
|
+
const code = s.charCodeAt(i);
|
|
4800
|
+
if (code >= 97 && code <= 122) return s.toUpperCase();
|
|
4801
|
+
}
|
|
4802
|
+
return s;
|
|
4803
|
+
}
|
|
4804
|
+
function refSplit(ref) {
|
|
4805
|
+
let i = 0;
|
|
4806
|
+
while (i < ref.length) {
|
|
4807
|
+
const code = ref.charCodeAt(i);
|
|
4808
|
+
if (code < 65 || code > 90) break;
|
|
4809
|
+
i++;
|
|
4810
|
+
}
|
|
4811
|
+
return i;
|
|
4812
|
+
}
|
|
4813
|
+
function columnFromRef(ref, split) {
|
|
4814
|
+
let col = 0;
|
|
4815
|
+
for (let i = 0; i < split; i++) col = col * 26 + (ref.charCodeAt(i) - 64);
|
|
4816
|
+
return col;
|
|
4817
|
+
}
|
|
4818
|
+
function translateRangeRef(ref, deltaRow, deltaCol) {
|
|
4819
|
+
const parts = ref.split(":").map((p) => {
|
|
4820
|
+
const { row, column } = parseCellRef(p);
|
|
4821
|
+
const r = row + deltaRow;
|
|
4822
|
+
const c = column + deltaCol;
|
|
4823
|
+
if (r < 1 || c < 1 || r > EXCEL_MAX_ROWS || c > EXCEL_MAX_COLUMNS) return null;
|
|
4824
|
+
return cellRefFromRowCol(r, c);
|
|
4825
|
+
});
|
|
4826
|
+
if (parts.some((p) => p === null)) return void 0;
|
|
4827
|
+
return parts.join(":");
|
|
4828
|
+
}
|
|
2754
4829
|
|
|
2755
4830
|
// src/model/defined-name.ts
|
|
2756
4831
|
var _names, _DefinedNameManager_instances, indexOf_fn;
|
|
@@ -2815,7 +4890,7 @@ indexOf_fn = function(name, scope) {
|
|
|
2815
4890
|
};
|
|
2816
4891
|
|
|
2817
4892
|
// src/model/workbook.ts
|
|
2818
|
-
var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
|
|
4893
|
+
var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _calcChain, _calcChainFingerprint, _deflateCache, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, structuralEdit_fn, sheetFingerprint_fn, calcChainStillValid_fn, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
|
|
2819
4894
|
var _Workbook = class _Workbook {
|
|
2820
4895
|
constructor(sharedStrings, styles) {
|
|
2821
4896
|
__privateAdd(this, _Workbook_instances);
|
|
@@ -2824,6 +4899,24 @@ var _Workbook = class _Workbook {
|
|
|
2824
4899
|
__privateAdd(this, _sheets, []);
|
|
2825
4900
|
__privateAdd(this, _definedNames, new DefinedNameManager());
|
|
2826
4901
|
__privateAdd(this, _closed, false);
|
|
4902
|
+
/**
|
|
4903
|
+
* The calculation order Excel stored in `xl/calcChain.xml`, in file order, or
|
|
4904
|
+
* undefined when the source had no such part. Read-only to consumers: it is a
|
|
4905
|
+
* record of what the source file said, not a live view of the model.
|
|
4906
|
+
*/
|
|
4907
|
+
__privateAdd(this, _calcChain);
|
|
4908
|
+
/**
|
|
4909
|
+
* Fingerprint of the sheet set taken right after the calc chain was parsed.
|
|
4910
|
+
* The chain is only written back out when this still matches — see
|
|
4911
|
+
* {@link #calcChainStillValid}.
|
|
4912
|
+
*/
|
|
4913
|
+
__privateAdd(this, _calcChainFingerprint);
|
|
4914
|
+
/**
|
|
4915
|
+
* Compressed form of each package part from the last save. Compression is where
|
|
4916
|
+
* a save spends its time, and an autosave changes one part, so the rest are
|
|
4917
|
+
* reused verbatim. Keyed on content, so a part rebuilt identically still hits.
|
|
4918
|
+
*/
|
|
4919
|
+
__privateAdd(this, _deflateCache, createDeflateCache());
|
|
2827
4920
|
/** Parts carried over verbatim from the file this workbook was opened from. */
|
|
2828
4921
|
__privateAdd(this, _preservedParts, /* @__PURE__ */ new Map());
|
|
2829
4922
|
__privateAdd(this, _preservedContentTypes);
|
|
@@ -2848,6 +4941,7 @@ var _Workbook = class _Workbook {
|
|
|
2848
4941
|
* Mirrors Workbook.Open(stream) in C#.
|
|
2849
4942
|
*/
|
|
2850
4943
|
static fromBuffer(buffer) {
|
|
4944
|
+
var _a;
|
|
2851
4945
|
const entries = unzipXlsx(buffer);
|
|
2852
4946
|
const ss = new SharedStringManager();
|
|
2853
4947
|
const sm = new StyleManager();
|
|
@@ -2875,10 +4969,20 @@ var _Workbook = class _Workbook {
|
|
|
2875
4969
|
const wsRelsXml = readXmlEntry(entries, `${dir}/_rels/${file}.rels`);
|
|
2876
4970
|
if (wsRelsXml) {
|
|
2877
4971
|
ws.loadPreservedState(wsXml, parseRelationships(wsRelsXml, wsPath));
|
|
4972
|
+
const commentsPath = ws.commentsPartPath();
|
|
4973
|
+
if (commentsPath) {
|
|
4974
|
+
const commentsXml = readXmlEntry(entries, commentsPath);
|
|
4975
|
+
if (commentsXml) ws.loadCommentsXml(commentsXml);
|
|
4976
|
+
}
|
|
2878
4977
|
}
|
|
2879
4978
|
}
|
|
2880
4979
|
__privateGet(wb, _sheets).push({ worksheet: ws, sheetId, relId });
|
|
2881
4980
|
}
|
|
4981
|
+
const calcChainXml = readXmlEntry(entries, CALC_CHAIN_PATH);
|
|
4982
|
+
if (calcChainXml) {
|
|
4983
|
+
__privateSet(wb, _calcChain, parseCalcChain(calcChainXml, __privateGet(wb, _sheets)));
|
|
4984
|
+
__privateSet(wb, _calcChainFingerprint, __privateMethod(_a = wb, _Workbook_instances, sheetFingerprint_fn).call(_a));
|
|
4985
|
+
}
|
|
2882
4986
|
__privateSet(wb, _preservedParts, collectPreservedParts(entries));
|
|
2883
4987
|
const ctXml = readXmlEntry(entries, "[Content_Types].xml");
|
|
2884
4988
|
if (ctXml) __privateSet(wb, _preservedContentTypes, parseContentTypes(ctXml));
|
|
@@ -2925,8 +5029,8 @@ var _Workbook = class _Workbook {
|
|
|
2925
5029
|
if (__privateGet(this, _sheets).some((e) => e.worksheet.name === name)) {
|
|
2926
5030
|
throw new Error(`Worksheet "${name}" already exists.`);
|
|
2927
5031
|
}
|
|
2928
|
-
const sheetId = __privateGet(this, _sheets).
|
|
2929
|
-
const relId = `rId${
|
|
5032
|
+
const sheetId = __privateGet(this, _sheets).reduce((max, e) => Math.max(max, e.sheetId), 0) + 1;
|
|
5033
|
+
const relId = `rId${__privateGet(this, _sheets).length + 1}`;
|
|
2930
5034
|
const ws = new Worksheet(name, __privateGet(this, _sharedStrings3), __privateGet(this, _styles3));
|
|
2931
5035
|
__privateGet(this, _sheets).push({ worksheet: ws, sheetId, relId });
|
|
2932
5036
|
return ws;
|
|
@@ -2963,6 +5067,32 @@ var _Workbook = class _Workbook {
|
|
|
2963
5067
|
if (!entry) throw new Error(`Worksheet "${name}" not found.`);
|
|
2964
5068
|
return entry.worksheet;
|
|
2965
5069
|
}
|
|
5070
|
+
// ── Structural editing (workbook-wide) ───────────────────────────────────────
|
|
5071
|
+
/**
|
|
5072
|
+
* Inserts rows on one sheet and keeps the whole workbook consistent:
|
|
5073
|
+
* formulas on OTHER sheets that reference the edited sheet, and defined
|
|
5074
|
+
* names, are rewritten too. Prefer this over `Worksheet.insertRows` in a
|
|
5075
|
+
* multi-sheet workbook.
|
|
5076
|
+
*
|
|
5077
|
+
* @returns The edited sheet's result; `refErrors` additionally includes
|
|
5078
|
+
* sheet-qualified refs (e.g. `"Other!B2"`) for affected formula
|
|
5079
|
+
* cells on other sheets.
|
|
5080
|
+
*/
|
|
5081
|
+
insertRows(sheetName, at, count = 1) {
|
|
5082
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "insert", at, count);
|
|
5083
|
+
}
|
|
5084
|
+
/** Workbook-wide counterpart to `Worksheet.deleteRows`. See {@link insertRows}. */
|
|
5085
|
+
deleteRows(sheetName, at, count = 1) {
|
|
5086
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "delete", at, count);
|
|
5087
|
+
}
|
|
5088
|
+
/** Workbook-wide counterpart to `Worksheet.insertColumns`. See {@link insertRows}. */
|
|
5089
|
+
insertColumns(sheetName, at, count = 1) {
|
|
5090
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "insert", at, count);
|
|
5091
|
+
}
|
|
5092
|
+
/** Workbook-wide counterpart to `Worksheet.deleteColumns`. See {@link insertRows}. */
|
|
5093
|
+
deleteColumns(sheetName, at, count = 1) {
|
|
5094
|
+
return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "delete", at, count);
|
|
5095
|
+
}
|
|
2966
5096
|
// ── Defined names (named ranges) ─────────────────────────────────────────────
|
|
2967
5097
|
/**
|
|
2968
5098
|
* Defines (or replaces) a named range.
|
|
@@ -3009,7 +5139,21 @@ var _Workbook = class _Workbook {
|
|
|
3009
5139
|
saveAsBuffer() {
|
|
3010
5140
|
__privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
|
|
3011
5141
|
const entries = __privateMethod(this, _Workbook_instances, buildZipEntries_fn).call(this);
|
|
3012
|
-
return
|
|
5142
|
+
return zipXlsxCached(entries, __privateGet(this, _deflateCache));
|
|
5143
|
+
}
|
|
5144
|
+
/**
|
|
5145
|
+
* Same output as {@link saveAsBuffer}, without blocking the calling thread on
|
|
5146
|
+
* compression — for an editor that autosaves, this is the difference between a
|
|
5147
|
+
* visible stall on every save and none.
|
|
5148
|
+
*
|
|
5149
|
+
* The XML for sheets that have not changed since the last save is reused, so a
|
|
5150
|
+
* save after a one-cell edit re-serialises one sheet rather than all of them.
|
|
5151
|
+
* That reuse applies to the synchronous path too; only the compression differs.
|
|
5152
|
+
*/
|
|
5153
|
+
async saveAsBufferAsync() {
|
|
5154
|
+
__privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
|
|
5155
|
+
const entries = __privateMethod(this, _Workbook_instances, buildZipEntries_fn).call(this);
|
|
5156
|
+
return zipXlsxCachedAsync(entries, __privateGet(this, _deflateCache));
|
|
3013
5157
|
}
|
|
3014
5158
|
/**
|
|
3015
5159
|
* Saves the workbook to a file (Node.js only).
|
|
@@ -3017,7 +5161,7 @@ var _Workbook = class _Workbook {
|
|
|
3017
5161
|
*/
|
|
3018
5162
|
async saveToFile(filePath) {
|
|
3019
5163
|
__privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
|
|
3020
|
-
const buffer = this.
|
|
5164
|
+
const buffer = await this.saveAsBufferAsync();
|
|
3021
5165
|
const fs = await import('fs/promises');
|
|
3022
5166
|
const path = await import('path');
|
|
3023
5167
|
const dir = path.dirname(filePath);
|
|
@@ -3033,16 +5177,72 @@ var _Workbook = class _Workbook {
|
|
|
3033
5177
|
[Symbol.dispose]() {
|
|
3034
5178
|
this.close();
|
|
3035
5179
|
}
|
|
5180
|
+
// ── Calculation order ──────────────────────────────────────────────────────
|
|
5181
|
+
/**
|
|
5182
|
+
* The calculation order Excel recorded for this workbook, oldest-first as
|
|
5183
|
+
* stored in `xl/calcChain.xml`, or an empty array when the source file had
|
|
5184
|
+
* none (a freshly created workbook, or one saved by a tool that omits it).
|
|
5185
|
+
*
|
|
5186
|
+
* Excel has already done the topological sort that produced this order, so
|
|
5187
|
+
* evaluating in it avoids repeating that work. Treat it as a hint, not a
|
|
5188
|
+
* guarantee: it reflects the file as opened, and edits made since are not
|
|
5189
|
+
* reflected here.
|
|
5190
|
+
*/
|
|
5191
|
+
get calcChain() {
|
|
5192
|
+
return __privateGet(this, _calcChain) ?? EMPTY_CALC_CHAIN;
|
|
5193
|
+
}
|
|
3036
5194
|
};
|
|
3037
5195
|
_sharedStrings3 = new WeakMap();
|
|
3038
5196
|
_styles3 = new WeakMap();
|
|
3039
5197
|
_sheets = new WeakMap();
|
|
3040
5198
|
_definedNames = new WeakMap();
|
|
3041
5199
|
_closed = new WeakMap();
|
|
5200
|
+
_calcChain = new WeakMap();
|
|
5201
|
+
_calcChainFingerprint = new WeakMap();
|
|
5202
|
+
_deflateCache = new WeakMap();
|
|
3042
5203
|
_preservedParts = new WeakMap();
|
|
3043
5204
|
_preservedContentTypes = new WeakMap();
|
|
3044
5205
|
_orphanedTargets = new WeakMap();
|
|
3045
5206
|
_Workbook_instances = new WeakSet();
|
|
5207
|
+
structuralEdit_fn = function(sheetName, dim, kind, at, count) {
|
|
5208
|
+
__privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
|
|
5209
|
+
const target = this.getWorksheet(sheetName);
|
|
5210
|
+
const result = dim === "row" ? kind === "insert" ? target.insertRows(at, count) : target.deleteRows(at, count) : kind === "insert" ? target.insertColumns(at, count) : target.deleteColumns(at, count);
|
|
5211
|
+
const shift = { dim, kind, at, count, sheetName: target.name };
|
|
5212
|
+
const refErrors = [...result.refErrors];
|
|
5213
|
+
for (const ws of this.worksheets) {
|
|
5214
|
+
if (ws === target) continue;
|
|
5215
|
+
const external = ws.applyExternalShift(shift);
|
|
5216
|
+
for (const ref of external.refErrors) refErrors.push(`${ws.name}!${ref}`);
|
|
5217
|
+
}
|
|
5218
|
+
for (const dn of [...__privateGet(this, _definedNames).all]) {
|
|
5219
|
+
const rewritten = shiftFormulaRefs(dn.refersTo, shift, dn.scope ?? "");
|
|
5220
|
+
if (rewritten.changed) __privateGet(this, _definedNames).define(dn.name, rewritten.text, dn.scope);
|
|
5221
|
+
}
|
|
5222
|
+
return { refErrors };
|
|
5223
|
+
};
|
|
5224
|
+
/**
|
|
5225
|
+
* Identity of the sheet set: names, durable ids and per-sheet mutation counts.
|
|
5226
|
+
* The calc chain is a statement about exactly this set — which cells hold
|
|
5227
|
+
* formulas, on which sheet, under which id — so any change to it invalidates
|
|
5228
|
+
* the chain wholesale.
|
|
5229
|
+
*/
|
|
5230
|
+
sheetFingerprint_fn = function() {
|
|
5231
|
+
return __privateGet(this, _sheets).map((e) => `${e.sheetId}:${e.worksheet.name}:${e.worksheet.version}`).join("|");
|
|
5232
|
+
};
|
|
5233
|
+
/**
|
|
5234
|
+
* Whether the parsed calc chain may be written back out.
|
|
5235
|
+
*
|
|
5236
|
+
* Deliberately conservative: any edit at all (a value, a formula, a sheet
|
|
5237
|
+
* added or removed) and the chain is dropped instead of patched. An absent
|
|
5238
|
+
* calcChain is a documented no-op — Excel rebuilds it on open — whereas a
|
|
5239
|
+
* chain that disagrees with the sheets is the failure mode this whole change
|
|
5240
|
+
* exists to remove. Rebuilding a correct chain would mean redoing Excel's
|
|
5241
|
+
* dependency analysis, which belongs in the formula package, not here.
|
|
5242
|
+
*/
|
|
5243
|
+
calcChainStillValid_fn = function() {
|
|
5244
|
+
return __privateGet(this, _calcChain) !== void 0 && __privateGet(this, _calcChain).length > 0 && __privateGet(this, _calcChainFingerprint) === __privateMethod(this, _Workbook_instances, sheetFingerprint_fn).call(this);
|
|
5245
|
+
};
|
|
3046
5246
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
3047
5247
|
assertOpen_fn = function() {
|
|
3048
5248
|
if (__privateGet(this, _closed)) throw new Error("Workbook has been closed.");
|
|
@@ -3103,7 +5303,7 @@ buildZipEntries_fn = function() {
|
|
|
3103
5303
|
entries.set("_rels/.rels", ROOT_RELS);
|
|
3104
5304
|
const sheetMetas = __privateGet(this, _sheets).map((e, i) => ({
|
|
3105
5305
|
name: e.worksheet.name,
|
|
3106
|
-
sheetId:
|
|
5306
|
+
sheetId: e.sheetId,
|
|
3107
5307
|
relationshipId: `rId${i + 1}`,
|
|
3108
5308
|
state: e.worksheet.visibility !== "visible" ? e.worksheet.visibility : void 0
|
|
3109
5309
|
}));
|
|
@@ -3116,20 +5316,58 @@ buildZipEntries_fn = function() {
|
|
|
3116
5316
|
return { name: dn.name, refersTo: dn.refersTo, localSheetId };
|
|
3117
5317
|
});
|
|
3118
5318
|
entries.set("xl/workbook.xml", buildWorkbookXml(sheetMetas, definedNameMetas));
|
|
3119
|
-
|
|
5319
|
+
const keepCalcChain = __privateMethod(this, _Workbook_instances, calcChainStillValid_fn).call(this);
|
|
5320
|
+
if (keepCalcChain) {
|
|
5321
|
+
const idBySheet = new Map(__privateGet(this, _sheets).map((e) => [e.worksheet.name, e.sheetId]));
|
|
5322
|
+
entries.set(CALC_CHAIN_PATH, buildCalcChainXml(__privateGet(this, _calcChain), idBySheet));
|
|
5323
|
+
} else {
|
|
5324
|
+
entries.delete(CALC_CHAIN_PATH);
|
|
5325
|
+
preservedPaths.delete(CALC_CHAIN_PATH);
|
|
5326
|
+
}
|
|
5327
|
+
entries.set("xl/_rels/workbook.xml.rels", buildWorkbookRels(sheetCount, keepCalcChain));
|
|
3120
5328
|
entries.set("xl/styles.xml", __privateGet(this, _styles3).buildStylesXml());
|
|
3121
5329
|
entries.set("xl/sharedStrings.xml", __privateGet(this, _sharedStrings3).buildXml());
|
|
3122
5330
|
for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
|
|
3123
5331
|
entries.set(`xl/worksheets/sheet${i + 1}.xml`, __privateGet(this, _sheets)[i].worksheet.buildXml());
|
|
3124
5332
|
}
|
|
3125
5333
|
const extraOverrides = [];
|
|
5334
|
+
const extraDefaults = [];
|
|
5335
|
+
if (keepCalcChain) {
|
|
5336
|
+
extraOverrides.push({ partName: `/${CALC_CHAIN_PATH}`, contentType: CONTENT_TYPES.calcChain });
|
|
5337
|
+
}
|
|
3126
5338
|
let drawingCounter = 0;
|
|
3127
5339
|
let partCounter = 0;
|
|
5340
|
+
let commentsCounter = 0;
|
|
3128
5341
|
for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
|
|
3129
5342
|
const ws = __privateGet(this, _sheets)[i].worksheet;
|
|
3130
5343
|
const sheetRels = ws.preservedRelationships.map(
|
|
3131
|
-
(r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${r.target}"${r.external ? ' TargetMode="External"' : ""}/>`
|
|
5344
|
+
(r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${escapeXml(r.target)}"${r.external ? ' TargetMode="External"' : ""}/>`
|
|
3132
5345
|
);
|
|
5346
|
+
const generated = ws.generatedRels();
|
|
5347
|
+
for (const link of ws.hyperlinks) {
|
|
5348
|
+
const relId = generated.hyperlinks.get(link.ref);
|
|
5349
|
+
if (!relId || !link.target) continue;
|
|
5350
|
+
sheetRels.push(
|
|
5351
|
+
` <Relationship Id="${relId}" Type="${REL_TYPES.hyperlink}" Target="${escapeXml(link.target)}" TargetMode="External"/>`
|
|
5352
|
+
);
|
|
5353
|
+
}
|
|
5354
|
+
if (generated.comments && generated.vmlDrawing) {
|
|
5355
|
+
do {
|
|
5356
|
+
commentsCounter++;
|
|
5357
|
+
} while (preservedPaths.has(`xl/comments${commentsCounter}.xml`) || preservedPaths.has(`xl/drawings/vmlDrawing${commentsCounter}.vml`));
|
|
5358
|
+
const commentsPath = `xl/comments${commentsCounter}.xml`;
|
|
5359
|
+
const vmlPath = `xl/drawings/vmlDrawing${commentsCounter}.vml`;
|
|
5360
|
+
entries.set(commentsPath, ws.buildCommentsXml());
|
|
5361
|
+
entries.set(vmlPath, ws.buildCommentsVml());
|
|
5362
|
+
extraOverrides.push({ partName: `/${commentsPath}`, contentType: CONTENT_TYPES.comments });
|
|
5363
|
+
if (!extraDefaults.some((d) => d.extension === "vml")) {
|
|
5364
|
+
extraDefaults.push({ extension: "vml", contentType: CONTENT_TYPES.vmlDrawing });
|
|
5365
|
+
}
|
|
5366
|
+
sheetRels.push(
|
|
5367
|
+
` <Relationship Id="${generated.comments}" Type="${REL_TYPES.comments}" Target="../comments${commentsCounter}.xml"/>`,
|
|
5368
|
+
` <Relationship Id="${generated.vmlDrawing}" Type="${REL_TYPES.vmlDrawing}" Target="../drawings/vmlDrawing${commentsCounter}.vml"/>`
|
|
5369
|
+
);
|
|
5370
|
+
}
|
|
3133
5371
|
if (ws.hasDrawings) {
|
|
3134
5372
|
do {
|
|
3135
5373
|
drawingCounter++;
|
|
@@ -3166,12 +5404,48 @@ buildZipEntries_fn = function() {
|
|
|
3166
5404
|
buildContentTypes(
|
|
3167
5405
|
sheetCount,
|
|
3168
5406
|
[...preservedCt.overrides, ...extraOverrides],
|
|
3169
|
-
preservedCt.defaults
|
|
5407
|
+
[...preservedCt.defaults, ...extraDefaults]
|
|
3170
5408
|
)
|
|
3171
5409
|
);
|
|
3172
5410
|
return entries;
|
|
3173
5411
|
};
|
|
3174
5412
|
var Workbook = _Workbook;
|
|
5413
|
+
var CALC_CHAIN_PATH = "xl/calcChain.xml";
|
|
5414
|
+
var EMPTY_CALC_CHAIN = [];
|
|
5415
|
+
function parseCalcChain(xml2, sheets) {
|
|
5416
|
+
const nameById = new Map(sheets.map((e) => [e.sheetId, e.worksheet.name]));
|
|
5417
|
+
const out = [];
|
|
5418
|
+
let currentId;
|
|
5419
|
+
try {
|
|
5420
|
+
const root = parseXml(xml2);
|
|
5421
|
+
for (const el of getElementsByTagName(root, "c")) {
|
|
5422
|
+
const ref = getAttr(el, "r");
|
|
5423
|
+
if (!ref) continue;
|
|
5424
|
+
const iAttr = getAttr(el, "i");
|
|
5425
|
+
if (iAttr !== void 0) {
|
|
5426
|
+
const parsed = parseInt(iAttr, 10);
|
|
5427
|
+
if (!Number.isNaN(parsed)) currentId = parsed;
|
|
5428
|
+
}
|
|
5429
|
+
if (currentId === void 0) continue;
|
|
5430
|
+
const sheet = nameById.get(currentId);
|
|
5431
|
+
if (sheet === void 0) continue;
|
|
5432
|
+
out.push({ sheet, ref: ref.toUpperCase() });
|
|
5433
|
+
}
|
|
5434
|
+
} catch {
|
|
5435
|
+
return [];
|
|
5436
|
+
}
|
|
5437
|
+
return out;
|
|
5438
|
+
}
|
|
5439
|
+
function buildCalcChainXml(chain, idBySheet) {
|
|
5440
|
+
const cells = [];
|
|
5441
|
+
for (const { sheet, ref } of chain) {
|
|
5442
|
+
const id = idBySheet.get(sheet);
|
|
5443
|
+
if (id === void 0) continue;
|
|
5444
|
+
cells.push(`<c r="${ref}" i="${id}"/>`);
|
|
5445
|
+
}
|
|
5446
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
5447
|
+
<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">${cells.join("")}</calcChain>`;
|
|
5448
|
+
}
|
|
3175
5449
|
function resolveWorksheetPath(relsXml, relId) {
|
|
3176
5450
|
if (!relsXml) return void 0;
|
|
3177
5451
|
try {
|
|
@@ -3191,6 +5465,8 @@ exports.CONTENT_TYPES = CONTENT_TYPES;
|
|
|
3191
5465
|
exports.Cell = Cell;
|
|
3192
5466
|
exports.CellStyle = CellStyle;
|
|
3193
5467
|
exports.DefinedNameManager = DefinedNameManager;
|
|
5468
|
+
exports.EXCEL_MAX_COLUMNS = EXCEL_MAX_COLUMNS;
|
|
5469
|
+
exports.EXCEL_MAX_ROWS = EXCEL_MAX_ROWS;
|
|
3194
5470
|
exports.ExcelAlignment = ExcelAlignment;
|
|
3195
5471
|
exports.ExcelBorder = ExcelBorder;
|
|
3196
5472
|
exports.ExcelBorderEdge = ExcelBorderEdge;
|
|
@@ -3219,9 +5495,12 @@ exports.cellRefFromRowCol = cellRefFromRowCol;
|
|
|
3219
5495
|
exports.columnLetter = columnLetter;
|
|
3220
5496
|
exports.columnNumber = columnNumber;
|
|
3221
5497
|
exports.decodeUtf8 = decodeUtf8;
|
|
5498
|
+
exports.decodeXmlEntities = decodeXmlEntities;
|
|
3222
5499
|
exports.encodeUtf8 = encodeUtf8;
|
|
3223
5500
|
exports.escapeXml = escapeXml;
|
|
5501
|
+
exports.formatDateCode = formatDateCode;
|
|
3224
5502
|
exports.formatNumberWithCode = formatNumberWithCode;
|
|
5503
|
+
exports.formatRangeRef = formatRangeRef;
|
|
3225
5504
|
exports.fromOADate = fromOADate;
|
|
3226
5505
|
exports.getAttr = getAttr;
|
|
3227
5506
|
exports.getElementsByTagName = getElementsByTagName;
|
|
@@ -3235,6 +5514,11 @@ exports.readXmlEntry = readXmlEntry;
|
|
|
3235
5514
|
exports.requireXmlEntry = requireXmlEntry;
|
|
3236
5515
|
exports.setBinaryEntry = setBinaryEntry;
|
|
3237
5516
|
exports.setXmlEntry = setXmlEntry;
|
|
5517
|
+
exports.shiftFormulaRefs = shiftFormulaRefs;
|
|
5518
|
+
exports.shiftIndex = shiftIndex;
|
|
5519
|
+
exports.shiftRangeRef = shiftRangeRef;
|
|
5520
|
+
exports.shiftSpan = shiftSpan;
|
|
5521
|
+
exports.shiftSqref = shiftSqref;
|
|
3238
5522
|
exports.toOADate = toOADate;
|
|
3239
5523
|
exports.unzipXlsx = unzipXlsx;
|
|
3240
5524
|
exports.xml = xml;
|