@odoo/o-spreadsheet 19.1.0-alpha.7 → 19.1.0-alpha.8
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/o-spreadsheet-engine.d.ts +8 -20
- package/dist/o-spreadsheet-engine.esm.js +471 -189
- package/dist/o-spreadsheet-engine.iife.js +471 -189
- package/dist/o-spreadsheet-engine.min.iife.js +313 -313
- package/dist/o-spreadsheet.d.ts +17 -23
- package/dist/o_spreadsheet.esm.js +696 -371
- package/dist/o_spreadsheet.iife.js +696 -371
- package/dist/o_spreadsheet.min.iife.js +317 -317
- package/dist/o_spreadsheet.xml +98 -9
- package/package.json +1 -1
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
5
|
* @version 19.1.0-alpha.3
|
|
6
|
-
* @date 2025-10-
|
|
7
|
-
* @hash
|
|
6
|
+
* @date 2025-10-23T08:19:27.355Z
|
|
7
|
+
* @hash 78717d4
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
class FunctionCodeBuilder {
|
|
@@ -5182,6 +5182,29 @@ profilesStartingPosition, profiles, zones, toRemove = false) {
|
|
|
5182
5182
|
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
5183
5183
|
}
|
|
5184
5184
|
}
|
|
5185
|
+
function profilesContainsZone(profilesStartingPosition, profiles, zone) {
|
|
5186
|
+
const leftValue = zone.left;
|
|
5187
|
+
const rightValue = zone.right;
|
|
5188
|
+
const topValue = zone.top;
|
|
5189
|
+
const bottomValue = zone.bottom + 1;
|
|
5190
|
+
const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
|
|
5191
|
+
const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
|
|
5192
|
+
if (leftIndex === -1 || rightIndex === -1) {
|
|
5193
|
+
return false;
|
|
5194
|
+
}
|
|
5195
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
5196
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
5197
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
|
|
5198
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
|
|
5199
|
+
if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
|
|
5200
|
+
return false;
|
|
5201
|
+
}
|
|
5202
|
+
if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
|
|
5203
|
+
return false;
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
return true;
|
|
5207
|
+
}
|
|
5185
5208
|
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
5186
5209
|
if (value === undefined) {
|
|
5187
5210
|
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
@@ -5266,7 +5289,18 @@ function modifyProfile(profile, zone, toRemove = false) {
|
|
|
5266
5289
|
}
|
|
5267
5290
|
// add the top and bottom value to the profile and
|
|
5268
5291
|
// remove all information between the top and bottom index
|
|
5269
|
-
|
|
5292
|
+
const toDelete = bottomSuccIndex - topPredIndex - 1;
|
|
5293
|
+
const toInsert = newPoints.length;
|
|
5294
|
+
const start = topPredIndex + 1;
|
|
5295
|
+
// fast path and slow path
|
|
5296
|
+
if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
|
|
5297
|
+
// fast path: we just need to replace the last element
|
|
5298
|
+
profile[start] = newPoints[0] ?? newPoints[1];
|
|
5299
|
+
}
|
|
5300
|
+
else {
|
|
5301
|
+
// equivalent but slower and with memory allocation
|
|
5302
|
+
profile.splice(start, toDelete, ...newPoints);
|
|
5303
|
+
}
|
|
5270
5304
|
}
|
|
5271
5305
|
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
5272
5306
|
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
@@ -5305,8 +5339,10 @@ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
|
|
|
5305
5339
|
left,
|
|
5306
5340
|
bottom,
|
|
5307
5341
|
right,
|
|
5308
|
-
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
5309
5342
|
};
|
|
5343
|
+
if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
|
|
5344
|
+
profileZone.hasHeader = true;
|
|
5345
|
+
}
|
|
5310
5346
|
let findCorrespondingZone = false;
|
|
5311
5347
|
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
5312
5348
|
const pendingZone = pendingZones[j];
|
|
@@ -5761,17 +5797,6 @@ function excludeTopLeft(zone) {
|
|
|
5761
5797
|
}
|
|
5762
5798
|
return [leftColumnZone, rightPartZone];
|
|
5763
5799
|
}
|
|
5764
|
-
function aggregatePositionsToZones(positions) {
|
|
5765
|
-
const result = {};
|
|
5766
|
-
for (const position of positions) {
|
|
5767
|
-
result[position.sheetId] ??= [];
|
|
5768
|
-
result[position.sheetId].push(positionToZone(position));
|
|
5769
|
-
}
|
|
5770
|
-
for (const sheetId in result) {
|
|
5771
|
-
result[sheetId] = recomputeZones(result[sheetId]);
|
|
5772
|
-
}
|
|
5773
|
-
return result;
|
|
5774
|
-
}
|
|
5775
5800
|
/**
|
|
5776
5801
|
* Array of all positions in the zone.
|
|
5777
5802
|
*/
|
|
@@ -14520,6 +14545,13 @@ pivotTimeAdapterRegistry
|
|
|
14520
14545
|
.add("minute_number", nullHandlerDecorator(minuteNumberAdapter))
|
|
14521
14546
|
.add("second_number", nullHandlerDecorator(secondNumberAdapter));
|
|
14522
14547
|
|
|
14548
|
+
const DEFAULT_PIVOT_STYLE = {
|
|
14549
|
+
displayTotals: true,
|
|
14550
|
+
displayColumnHeaders: true,
|
|
14551
|
+
displayMeasuresRow: true,
|
|
14552
|
+
numberOfRows: Number.MAX_VALUE,
|
|
14553
|
+
numberOfColumns: Number.MAX_VALUE,
|
|
14554
|
+
};
|
|
14523
14555
|
const AGGREGATOR_NAMES = {
|
|
14524
14556
|
count: _t("Count"),
|
|
14525
14557
|
count_distinct: _t("Count Distinct"),
|
|
@@ -14815,6 +14847,25 @@ function togglePivotCollapse(position, env) {
|
|
|
14815
14847
|
pivot: { ...definition, collapsedDomains: newDomains },
|
|
14816
14848
|
});
|
|
14817
14849
|
}
|
|
14850
|
+
function getPivotStyleFromFnArgs(definition, rowCountArg, includeTotalArg, includeColumnHeadersArg, columnCountArg, includeMeasuresRowArg, locale) {
|
|
14851
|
+
const style = definition.style;
|
|
14852
|
+
const numberOfRows = rowCountArg !== undefined
|
|
14853
|
+
? toNumber(rowCountArg, locale)
|
|
14854
|
+
: style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
|
|
14855
|
+
const numberOfColumns = columnCountArg !== undefined
|
|
14856
|
+
? toNumber(columnCountArg, locale)
|
|
14857
|
+
: style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;
|
|
14858
|
+
const displayTotals = includeTotalArg !== undefined
|
|
14859
|
+
? toBoolean(includeTotalArg)
|
|
14860
|
+
: style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
|
|
14861
|
+
const displayColumnHeaders = includeColumnHeadersArg !== undefined
|
|
14862
|
+
? toBoolean(includeColumnHeadersArg)
|
|
14863
|
+
: style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
|
|
14864
|
+
const displayMeasuresRow = includeMeasuresRowArg !== undefined
|
|
14865
|
+
? toBoolean(includeMeasuresRowArg)
|
|
14866
|
+
: style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;
|
|
14867
|
+
return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
|
|
14868
|
+
}
|
|
14818
14869
|
|
|
14819
14870
|
/**
|
|
14820
14871
|
* Get the pivot ID from the formula pivot ID.
|
|
@@ -15429,24 +15480,18 @@ const PIVOT = {
|
|
|
15429
15480
|
arg("column_count (number, optional)", _t("number of columns")),
|
|
15430
15481
|
arg("include_measure_titles (boolean, default=TRUE)", _t("Whether to include the measure titles row or not.")),
|
|
15431
15482
|
],
|
|
15432
|
-
compute: function (pivotFormulaId, rowCount
|
|
15483
|
+
compute: function (pivotFormulaId, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles) {
|
|
15433
15484
|
const _pivotFormulaId = toString(pivotFormulaId);
|
|
15434
|
-
const
|
|
15435
|
-
|
|
15485
|
+
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15486
|
+
const pivot = this.getters.getPivot(pivotId);
|
|
15487
|
+
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15488
|
+
const pivotStyle = getPivotStyleFromFnArgs(coreDefinition, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles, this.locale);
|
|
15489
|
+
if (pivotStyle.numberOfRows < 0) {
|
|
15436
15490
|
return new EvaluationError(_t("The number of rows must be positive."));
|
|
15437
15491
|
}
|
|
15438
|
-
|
|
15439
|
-
if (_columnCount < 0) {
|
|
15492
|
+
if (pivotStyle.numberOfColumns < 0) {
|
|
15440
15493
|
return new EvaluationError(_t("The number of columns must be positive."));
|
|
15441
15494
|
}
|
|
15442
|
-
const visibilityOptions = {
|
|
15443
|
-
displayColumnHeaders: toBoolean(includeColumnHeaders),
|
|
15444
|
-
displayTotals: toBoolean(includeTotal),
|
|
15445
|
-
displayMeasuresRow: toBoolean(includeMeasureTitles),
|
|
15446
|
-
};
|
|
15447
|
-
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15448
|
-
const pivot = this.getters.getPivot(pivotId);
|
|
15449
|
-
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15450
15495
|
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
|
|
15451
15496
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
15452
15497
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
@@ -15457,20 +15502,20 @@ const PIVOT = {
|
|
|
15457
15502
|
if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
|
|
15458
15503
|
return new EvaluationError(getPivotTooBigErrorMessage(table.numberOfCells, this.locale));
|
|
15459
15504
|
}
|
|
15460
|
-
const cells = table.getPivotCells(
|
|
15505
|
+
const cells = table.getPivotCells(pivotStyle);
|
|
15461
15506
|
let headerRows = 0;
|
|
15462
|
-
if (
|
|
15507
|
+
if (pivotStyle.displayColumnHeaders) {
|
|
15463
15508
|
headerRows = table.columns.length - 1;
|
|
15464
15509
|
}
|
|
15465
|
-
if (
|
|
15510
|
+
if (pivotStyle.displayMeasuresRow) {
|
|
15466
15511
|
headerRows++;
|
|
15467
15512
|
}
|
|
15468
15513
|
const pivotTitle = this.getters.getPivotName(pivotId);
|
|
15469
|
-
const tableHeight = Math.min(headerRows +
|
|
15514
|
+
const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
|
|
15470
15515
|
if (tableHeight === 0) {
|
|
15471
15516
|
return [[{ value: pivotTitle }]];
|
|
15472
15517
|
}
|
|
15473
|
-
const tableWidth = Math.min(1 +
|
|
15518
|
+
const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
|
|
15474
15519
|
const result = [];
|
|
15475
15520
|
for (const col of range(0, tableWidth)) {
|
|
15476
15521
|
result[col] = [];
|
|
@@ -15493,7 +15538,7 @@ const PIVOT = {
|
|
|
15493
15538
|
}
|
|
15494
15539
|
}
|
|
15495
15540
|
}
|
|
15496
|
-
if (
|
|
15541
|
+
if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
|
|
15497
15542
|
result[0][0] = { value: pivotTitle };
|
|
15498
15543
|
}
|
|
15499
15544
|
return result;
|
|
@@ -17965,6 +18010,10 @@ function getRangeParts(xc, zone) {
|
|
|
17965
18010
|
}
|
|
17966
18011
|
return parts;
|
|
17967
18012
|
}
|
|
18013
|
+
function positionToBoundedRange(position) {
|
|
18014
|
+
const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
|
|
18015
|
+
return { sheetId: position.sheetId, zone };
|
|
18016
|
+
}
|
|
17968
18017
|
/**
|
|
17969
18018
|
* Check that a zone is valid regarding the order of top-bottom and left-right.
|
|
17970
18019
|
* Left should be smaller than right, top should be smaller than bottom.
|
|
@@ -26653,6 +26702,7 @@ function forceUnicityOfFigure(data) {
|
|
|
26653
26702
|
return data;
|
|
26654
26703
|
}
|
|
26655
26704
|
const figureIds = new Set();
|
|
26705
|
+
const chartIds = new Set();
|
|
26656
26706
|
const uuidGenerator = new UuidGenerator();
|
|
26657
26707
|
for (const sheet of data.sheets || []) {
|
|
26658
26708
|
for (const figure of sheet.figures || []) {
|
|
@@ -26660,6 +26710,12 @@ function forceUnicityOfFigure(data) {
|
|
|
26660
26710
|
figure.id += uuidGenerator.smallUuid();
|
|
26661
26711
|
}
|
|
26662
26712
|
figureIds.add(figure.id);
|
|
26713
|
+
if (figure.tag === "chart") {
|
|
26714
|
+
if (chartIds.has(figure.data?.chartId)) {
|
|
26715
|
+
figure.data.chartId += uuidGenerator.smallUuid();
|
|
26716
|
+
}
|
|
26717
|
+
chartIds.add(figure.data?.chartId);
|
|
26718
|
+
}
|
|
26663
26719
|
}
|
|
26664
26720
|
}
|
|
26665
26721
|
data.uniqueFigureIds = true;
|
|
@@ -27027,11 +27083,6 @@ class CorePlugin extends BasePlugin {
|
|
|
27027
27083
|
* @param sheetName couple of old and new sheet names to adapt ranges pointing to that sheet
|
|
27028
27084
|
*/
|
|
27029
27085
|
adaptRanges(applyChange, sheetId, sheetName) { }
|
|
27030
|
-
/**
|
|
27031
|
-
* Implement this method to clean unused external resources, such as images
|
|
27032
|
-
* stored on a server which have been deleted.
|
|
27033
|
-
*/
|
|
27034
|
-
garbageCollectExternalResources() { }
|
|
27035
27086
|
}
|
|
27036
27087
|
|
|
27037
27088
|
class BordersPlugin extends CorePlugin {
|
|
@@ -30875,17 +30926,6 @@ class ImagePlugin extends CorePlugin {
|
|
|
30875
30926
|
break;
|
|
30876
30927
|
}
|
|
30877
30928
|
}
|
|
30878
|
-
/**
|
|
30879
|
-
* Delete unused images from the file store
|
|
30880
|
-
*/
|
|
30881
|
-
garbageCollectExternalResources() {
|
|
30882
|
-
const images = new Set(this.getAllImages().map((image) => image.path));
|
|
30883
|
-
for (const path of this.syncedImages) {
|
|
30884
|
-
if (!images.has(path)) {
|
|
30885
|
-
this.fileStore?.delete(path);
|
|
30886
|
-
}
|
|
30887
|
-
}
|
|
30888
|
-
}
|
|
30889
30929
|
// ---------------------------------------------------------------------------
|
|
30890
30930
|
// Getters
|
|
30891
30931
|
// ---------------------------------------------------------------------------
|
|
@@ -30947,13 +30987,6 @@ class ImagePlugin extends CorePlugin {
|
|
|
30947
30987
|
sheet.images = [...sheet.images, ...images];
|
|
30948
30988
|
}
|
|
30949
30989
|
}
|
|
30950
|
-
getAllImages() {
|
|
30951
|
-
const images = [];
|
|
30952
|
-
for (const sheetId in this.images) {
|
|
30953
|
-
images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined));
|
|
30954
|
-
}
|
|
30955
|
-
return images;
|
|
30956
|
-
}
|
|
30957
30990
|
}
|
|
30958
30991
|
|
|
30959
30992
|
class MergePlugin extends CorePlugin {
|
|
@@ -31729,26 +31762,22 @@ class SpreadsheetPivotTable {
|
|
|
31729
31762
|
getNumberOfDataColumns() {
|
|
31730
31763
|
return this.columns.at(-1)?.length || 0;
|
|
31731
31764
|
}
|
|
31732
|
-
getSkippedRows(
|
|
31765
|
+
getSkippedRows(pivotStyle) {
|
|
31733
31766
|
const skippedRows = new Set();
|
|
31734
|
-
if (!
|
|
31767
|
+
if (!pivotStyle.displayColumnHeaders) {
|
|
31735
31768
|
for (let i = 0; i < this.columns.length - 1; i++) {
|
|
31736
31769
|
skippedRows.add(i);
|
|
31737
31770
|
}
|
|
31738
31771
|
}
|
|
31739
|
-
if (!
|
|
31772
|
+
if (!pivotStyle.displayMeasuresRow) {
|
|
31740
31773
|
skippedRows.add(this.columns.length - 1);
|
|
31741
31774
|
}
|
|
31742
31775
|
return skippedRows;
|
|
31743
31776
|
}
|
|
31744
|
-
getPivotCells(
|
|
31745
|
-
|
|
31746
|
-
displayTotals: true,
|
|
31747
|
-
displayMeasuresRow: true,
|
|
31748
|
-
}) {
|
|
31749
|
-
const key = JSON.stringify(visibilityOptions);
|
|
31777
|
+
getPivotCells(pivotStyle = DEFAULT_PIVOT_STYLE) {
|
|
31778
|
+
const key = JSON.stringify(pivotStyle);
|
|
31750
31779
|
if (!this.pivotCells[key]) {
|
|
31751
|
-
const { displayTotals } =
|
|
31780
|
+
const { displayTotals } = pivotStyle;
|
|
31752
31781
|
const numberOfDataRows = this.rows.length;
|
|
31753
31782
|
const numberOfDataColumns = this.getNumberOfDataColumns();
|
|
31754
31783
|
let pivotHeight = this.columns.length + numberOfDataRows;
|
|
@@ -31760,7 +31789,7 @@ class SpreadsheetPivotTable {
|
|
|
31760
31789
|
pivotWidth -= this.measures.length;
|
|
31761
31790
|
}
|
|
31762
31791
|
const domainArray = [];
|
|
31763
|
-
const skippedRows = this.getSkippedRows(
|
|
31792
|
+
const skippedRows = this.getSkippedRows(pivotStyle);
|
|
31764
31793
|
for (let col = 0; col < pivotWidth; col++) {
|
|
31765
31794
|
domainArray.push([]);
|
|
31766
31795
|
for (let row = 0; row < pivotHeight; row++) {
|
|
@@ -34879,6 +34908,281 @@ class ZoneRBush extends RBush {
|
|
|
34879
34908
|
}
|
|
34880
34909
|
}
|
|
34881
34910
|
|
|
34911
|
+
class ZoneSet {
|
|
34912
|
+
profilesStartingPosition = [0];
|
|
34913
|
+
profiles = new Map([[0, []]]);
|
|
34914
|
+
constructor(zones = []) {
|
|
34915
|
+
for (const zone of zones) {
|
|
34916
|
+
this.add(zone);
|
|
34917
|
+
}
|
|
34918
|
+
}
|
|
34919
|
+
isEmpty() {
|
|
34920
|
+
return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
|
|
34921
|
+
}
|
|
34922
|
+
add(zone) {
|
|
34923
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
|
|
34924
|
+
}
|
|
34925
|
+
delete(zone) {
|
|
34926
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
|
|
34927
|
+
}
|
|
34928
|
+
has(zone) {
|
|
34929
|
+
return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
|
|
34930
|
+
}
|
|
34931
|
+
difference(other) {
|
|
34932
|
+
const result = this.copy();
|
|
34933
|
+
for (const zone of other) {
|
|
34934
|
+
result.delete(zone);
|
|
34935
|
+
}
|
|
34936
|
+
return result;
|
|
34937
|
+
}
|
|
34938
|
+
copy() {
|
|
34939
|
+
const result = new ZoneSet();
|
|
34940
|
+
result.profilesStartingPosition = [...this.profilesStartingPosition];
|
|
34941
|
+
result.profiles = new Map();
|
|
34942
|
+
for (const [key, value] of this.profiles) {
|
|
34943
|
+
result.profiles.set(key, [...value]);
|
|
34944
|
+
}
|
|
34945
|
+
return result;
|
|
34946
|
+
}
|
|
34947
|
+
size() {
|
|
34948
|
+
let size = 0;
|
|
34949
|
+
for (const profile of this.profiles.values()) {
|
|
34950
|
+
size += profile.length;
|
|
34951
|
+
}
|
|
34952
|
+
return size / 2;
|
|
34953
|
+
}
|
|
34954
|
+
/**
|
|
34955
|
+
* iterator of all the zones in the ZoneSet
|
|
34956
|
+
*/
|
|
34957
|
+
[Symbol.iterator]() {
|
|
34958
|
+
return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
|
|
34959
|
+
}
|
|
34960
|
+
}
|
|
34961
|
+
|
|
34962
|
+
class RangeSet {
|
|
34963
|
+
setsBySheetId = {};
|
|
34964
|
+
constructor(ranges = []) {
|
|
34965
|
+
for (const range of ranges) {
|
|
34966
|
+
this.add(range);
|
|
34967
|
+
}
|
|
34968
|
+
}
|
|
34969
|
+
add(range) {
|
|
34970
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
34971
|
+
this.setsBySheetId[range.sheetId] = new ZoneSet();
|
|
34972
|
+
}
|
|
34973
|
+
this.setsBySheetId[range.sheetId].add(range.zone);
|
|
34974
|
+
}
|
|
34975
|
+
addMany(ranges) {
|
|
34976
|
+
for (const range of ranges) {
|
|
34977
|
+
this.add(range);
|
|
34978
|
+
}
|
|
34979
|
+
}
|
|
34980
|
+
addPosition(position) {
|
|
34981
|
+
this.add(positionToBoundedRange(position));
|
|
34982
|
+
}
|
|
34983
|
+
addManyPositions(positions) {
|
|
34984
|
+
for (const position of positions) {
|
|
34985
|
+
this.addPosition(position);
|
|
34986
|
+
}
|
|
34987
|
+
}
|
|
34988
|
+
has(range) {
|
|
34989
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
34990
|
+
return false;
|
|
34991
|
+
}
|
|
34992
|
+
return this.setsBySheetId[range.sheetId].has(range.zone);
|
|
34993
|
+
}
|
|
34994
|
+
hasPosition(position) {
|
|
34995
|
+
return this.has(positionToBoundedRange(position));
|
|
34996
|
+
}
|
|
34997
|
+
delete(range) {
|
|
34998
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
34999
|
+
return;
|
|
35000
|
+
}
|
|
35001
|
+
this.setsBySheetId[range.sheetId].delete(range.zone);
|
|
35002
|
+
}
|
|
35003
|
+
deleteMany(ranges) {
|
|
35004
|
+
for (const range of ranges) {
|
|
35005
|
+
this.delete(range);
|
|
35006
|
+
}
|
|
35007
|
+
}
|
|
35008
|
+
deleteManyPositions(positions) {
|
|
35009
|
+
for (const position of positions) {
|
|
35010
|
+
this.delete(positionToBoundedRange(position));
|
|
35011
|
+
}
|
|
35012
|
+
}
|
|
35013
|
+
difference(other) {
|
|
35014
|
+
const result = new RangeSet();
|
|
35015
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35016
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
|
|
35017
|
+
}
|
|
35018
|
+
for (const sheetId in other.setsBySheetId) {
|
|
35019
|
+
if (result.setsBySheetId[sheetId]) {
|
|
35020
|
+
result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
|
|
35021
|
+
}
|
|
35022
|
+
}
|
|
35023
|
+
return result;
|
|
35024
|
+
}
|
|
35025
|
+
copy() {
|
|
35026
|
+
const result = new RangeSet();
|
|
35027
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35028
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
|
|
35029
|
+
}
|
|
35030
|
+
return result;
|
|
35031
|
+
}
|
|
35032
|
+
clear() {
|
|
35033
|
+
this.setsBySheetId = {};
|
|
35034
|
+
}
|
|
35035
|
+
size() {
|
|
35036
|
+
let size = 0;
|
|
35037
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35038
|
+
size += this.setsBySheetId[sheetId].size();
|
|
35039
|
+
}
|
|
35040
|
+
return size;
|
|
35041
|
+
}
|
|
35042
|
+
isEmpty() {
|
|
35043
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35044
|
+
if (!this.setsBySheetId[sheetId].isEmpty()) {
|
|
35045
|
+
return false;
|
|
35046
|
+
}
|
|
35047
|
+
}
|
|
35048
|
+
return true;
|
|
35049
|
+
}
|
|
35050
|
+
/**
|
|
35051
|
+
* iterator of all the ranges in the RangeSet
|
|
35052
|
+
*/
|
|
35053
|
+
[Symbol.iterator]() {
|
|
35054
|
+
const result = [];
|
|
35055
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35056
|
+
for (const zone of this.setsBySheetId[sheetId]) {
|
|
35057
|
+
result.push({ sheetId: sheetId, zone });
|
|
35058
|
+
}
|
|
35059
|
+
}
|
|
35060
|
+
return result[Symbol.iterator]();
|
|
35061
|
+
}
|
|
35062
|
+
}
|
|
35063
|
+
|
|
35064
|
+
/**
|
|
35065
|
+
* R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
|
|
35066
|
+
* Ranges associated to the exact same bounding box are grouped together
|
|
35067
|
+
* to reduce the number of nodes in the R-tree.
|
|
35068
|
+
*/
|
|
35069
|
+
class DependenciesRTree {
|
|
35070
|
+
rTree;
|
|
35071
|
+
constructor(items = []) {
|
|
35072
|
+
const compactedBoxes = groupSameBoundingBoxes(items);
|
|
35073
|
+
this.rTree = new SpreadsheetRTree(compactedBoxes);
|
|
35074
|
+
}
|
|
35075
|
+
insert(item) {
|
|
35076
|
+
const data = this.rTree.search(item.boundingBox);
|
|
35077
|
+
const itemBoundingBox = item.boundingBox;
|
|
35078
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
35079
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
35080
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
35081
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
35082
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
35083
|
+
if (exactBoundingBox) {
|
|
35084
|
+
exactBoundingBox.data.add(item.data);
|
|
35085
|
+
}
|
|
35086
|
+
else {
|
|
35087
|
+
this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
|
|
35088
|
+
}
|
|
35089
|
+
}
|
|
35090
|
+
search({ zone, sheetId }) {
|
|
35091
|
+
const results = new RangeSet();
|
|
35092
|
+
for (const { data } of this.rTree.search({ zone, sheetId })) {
|
|
35093
|
+
results.addMany(data);
|
|
35094
|
+
}
|
|
35095
|
+
return results;
|
|
35096
|
+
}
|
|
35097
|
+
remove(item) {
|
|
35098
|
+
const data = this.rTree.search(item.boundingBox);
|
|
35099
|
+
const itemBoundingBox = item.boundingBox;
|
|
35100
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
35101
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
35102
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
35103
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
35104
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
35105
|
+
if (exactBoundingBox) {
|
|
35106
|
+
exactBoundingBox.data.delete(item.data);
|
|
35107
|
+
}
|
|
35108
|
+
else {
|
|
35109
|
+
this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
|
|
35110
|
+
}
|
|
35111
|
+
}
|
|
35112
|
+
}
|
|
35113
|
+
/**
|
|
35114
|
+
* Group together all formulas pointing to the exact same dependency (bounding box).
|
|
35115
|
+
* The goal is to optimize the following case:
|
|
35116
|
+
* - if any cell in B1:B1000 changes, C1 must be recomputed
|
|
35117
|
+
* - if any cell in B1:B1000 changes, C2 must be recomputed
|
|
35118
|
+
* - if any cell in B1:B1000 changes, C3 must be recomputed
|
|
35119
|
+
* ...
|
|
35120
|
+
* - if any cell in B1:B1000 changes, C1000 must be recomputed
|
|
35121
|
+
*
|
|
35122
|
+
* Instead of having 1000 entries in the R-tree, we want to have a single entry
|
|
35123
|
+
* with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
|
|
35124
|
+
*/
|
|
35125
|
+
function groupSameBoundingBoxes(items) {
|
|
35126
|
+
// Important: this function must be as fast as possible. It is on the evaluation hot path.
|
|
35127
|
+
let maxCol = 0;
|
|
35128
|
+
let maxRow = 0;
|
|
35129
|
+
for (let i = 0; i < items.length; i++) {
|
|
35130
|
+
const zone = items[i].boundingBox.zone;
|
|
35131
|
+
if (zone.right > maxCol) {
|
|
35132
|
+
maxCol = zone.right;
|
|
35133
|
+
}
|
|
35134
|
+
if (zone.bottom > maxRow) {
|
|
35135
|
+
maxRow = zone.bottom;
|
|
35136
|
+
}
|
|
35137
|
+
}
|
|
35138
|
+
maxCol += 1;
|
|
35139
|
+
maxRow += 1;
|
|
35140
|
+
// in most real-world cases, we can use a fast numeric key
|
|
35141
|
+
// but if the zones are too far right or bottom, we fallback to a slower string key
|
|
35142
|
+
const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
|
|
35143
|
+
const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
|
|
35144
|
+
if (!useFastKey) {
|
|
35145
|
+
console.warn("Max col/row size exceeded, using slow zone key");
|
|
35146
|
+
}
|
|
35147
|
+
const groupedByBBox = {};
|
|
35148
|
+
for (const item of items) {
|
|
35149
|
+
const sheetId = item.boundingBox.sheetId;
|
|
35150
|
+
if (!groupedByBBox[sheetId]) {
|
|
35151
|
+
groupedByBBox[sheetId] = {};
|
|
35152
|
+
}
|
|
35153
|
+
const bBox = item.boundingBox.zone;
|
|
35154
|
+
let bBoxKey = 0;
|
|
35155
|
+
if (useFastKey) {
|
|
35156
|
+
bBoxKey =
|
|
35157
|
+
bBox.left +
|
|
35158
|
+
bBox.top * maxCol +
|
|
35159
|
+
bBox.right * maxCol * maxRow +
|
|
35160
|
+
bBox.bottom * maxCol * maxRow * maxCol;
|
|
35161
|
+
}
|
|
35162
|
+
else {
|
|
35163
|
+
bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
|
|
35164
|
+
}
|
|
35165
|
+
if (groupedByBBox[sheetId][bBoxKey]) {
|
|
35166
|
+
const ranges = groupedByBBox[sheetId][bBoxKey].data;
|
|
35167
|
+
ranges.add(item.data);
|
|
35168
|
+
}
|
|
35169
|
+
else {
|
|
35170
|
+
groupedByBBox[sheetId][bBoxKey] = {
|
|
35171
|
+
boundingBox: item.boundingBox,
|
|
35172
|
+
data: new RangeSet([item.data]),
|
|
35173
|
+
};
|
|
35174
|
+
}
|
|
35175
|
+
}
|
|
35176
|
+
const result = [];
|
|
35177
|
+
for (const sheetId in groupedByBBox) {
|
|
35178
|
+
const map = groupedByBBox[sheetId];
|
|
35179
|
+
for (const key in map) {
|
|
35180
|
+
result.push(map[key]);
|
|
35181
|
+
}
|
|
35182
|
+
}
|
|
35183
|
+
return result;
|
|
35184
|
+
}
|
|
35185
|
+
|
|
34882
35186
|
/**
|
|
34883
35187
|
* Implementation of a dependency Graph.
|
|
34884
35188
|
* The graph is used to evaluate the cells in the correct
|
|
@@ -34887,12 +35191,10 @@ class ZoneRBush extends RBush {
|
|
|
34887
35191
|
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
34888
35192
|
*/
|
|
34889
35193
|
class FormulaDependencyGraph {
|
|
34890
|
-
createEmptyPositionSet;
|
|
34891
35194
|
dependencies = new PositionMap();
|
|
34892
35195
|
rTree;
|
|
34893
|
-
constructor(
|
|
34894
|
-
this.
|
|
34895
|
-
this.rTree = new SpreadsheetRTree(data);
|
|
35196
|
+
constructor(data = []) {
|
|
35197
|
+
this.rTree = new DependenciesRTree(data);
|
|
34896
35198
|
}
|
|
34897
35199
|
removeAllDependencies(formulaPosition) {
|
|
34898
35200
|
const ranges = this.dependencies.get(formulaPosition);
|
|
@@ -34906,7 +35208,10 @@ class FormulaDependencyGraph {
|
|
|
34906
35208
|
}
|
|
34907
35209
|
addDependencies(formulaPosition, dependencies) {
|
|
34908
35210
|
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
34909
|
-
data:
|
|
35211
|
+
data: {
|
|
35212
|
+
sheetId: formulaPosition.sheetId,
|
|
35213
|
+
zone: positionToZone(formulaPosition),
|
|
35214
|
+
},
|
|
34910
35215
|
boundingBox: {
|
|
34911
35216
|
zone,
|
|
34912
35217
|
sheetId,
|
|
@@ -34924,46 +35229,20 @@ class FormulaDependencyGraph {
|
|
|
34924
35229
|
}
|
|
34925
35230
|
}
|
|
34926
35231
|
/**
|
|
34927
|
-
* Return all the cells that depend on the provided ranges
|
|
34928
|
-
* in the correct order they should be evaluated.
|
|
34929
|
-
* This is called a topological ordering (excluding cycles)
|
|
35232
|
+
* Return all the cells that depend on the provided ranges.
|
|
34930
35233
|
*/
|
|
34931
|
-
getCellsDependingOn(ranges,
|
|
34932
|
-
|
|
35234
|
+
getCellsDependingOn(ranges, visited = new RangeSet()) {
|
|
35235
|
+
visited = visited.copy();
|
|
34933
35236
|
const queue = Array.from(ranges).reverse();
|
|
34934
35237
|
while (queue.length > 0) {
|
|
34935
35238
|
const range = queue.pop();
|
|
34936
|
-
|
|
34937
|
-
const
|
|
34938
|
-
|
|
34939
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
34940
|
-
visited.add({ sheetId, col, row });
|
|
34941
|
-
}
|
|
34942
|
-
}
|
|
34943
|
-
const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
|
|
34944
|
-
const nextInQueue = {};
|
|
34945
|
-
for (const position of impactedPositions) {
|
|
34946
|
-
if (!visited.has(position) && !ignore.has(position)) {
|
|
34947
|
-
if (!nextInQueue[position.sheetId]) {
|
|
34948
|
-
nextInQueue[position.sheetId] = [];
|
|
34949
|
-
}
|
|
34950
|
-
nextInQueue[position.sheetId].push(positionToZone(position));
|
|
34951
|
-
}
|
|
34952
|
-
}
|
|
34953
|
-
for (const sheetId in nextInQueue) {
|
|
34954
|
-
const zones = recomputeZones(nextInQueue[sheetId], []);
|
|
34955
|
-
queue.push(...zones.map((zone) => ({ sheetId, zone })));
|
|
34956
|
-
}
|
|
35239
|
+
visited.add(range);
|
|
35240
|
+
const impactedRanges = this.rTree.search(range);
|
|
35241
|
+
queue.push(...impactedRanges.difference(visited));
|
|
34957
35242
|
}
|
|
34958
35243
|
// remove initial ranges
|
|
34959
35244
|
for (const range of ranges) {
|
|
34960
|
-
|
|
34961
|
-
const sheetId = range.sheetId;
|
|
34962
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
34963
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
34964
|
-
visited.delete({ sheetId, col, row });
|
|
34965
|
-
}
|
|
34966
|
-
}
|
|
35245
|
+
visited.delete(range);
|
|
34967
35246
|
}
|
|
34968
35247
|
return visited;
|
|
34969
35248
|
}
|
|
@@ -35240,7 +35519,7 @@ class Evaluator {
|
|
|
35240
35519
|
getters;
|
|
35241
35520
|
compilationParams;
|
|
35242
35521
|
evaluatedCells = new PositionMap();
|
|
35243
|
-
formulaDependencies = lazy(new FormulaDependencyGraph(
|
|
35522
|
+
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
35244
35523
|
blockedArrayFormulas = new PositionSet({});
|
|
35245
35524
|
spreadingRelations = new SpreadingRelation();
|
|
35246
35525
|
constructor(context, getters) {
|
|
@@ -35275,7 +35554,7 @@ class Evaluator {
|
|
|
35275
35554
|
return undefined;
|
|
35276
35555
|
}
|
|
35277
35556
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
|
|
35278
|
-
return
|
|
35557
|
+
return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
|
|
35279
35558
|
}
|
|
35280
35559
|
updateDependencies(position) {
|
|
35281
35560
|
// removing dependencies is slow because it requires
|
|
@@ -35319,57 +35598,72 @@ class Evaluator {
|
|
|
35319
35598
|
}
|
|
35320
35599
|
evaluateCells(positions) {
|
|
35321
35600
|
const start = performance.now();
|
|
35322
|
-
const
|
|
35323
|
-
|
|
35601
|
+
const rangesToCompute = new RangeSet();
|
|
35602
|
+
rangesToCompute.addManyPositions(positions);
|
|
35324
35603
|
const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
|
|
35325
|
-
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
this.evaluate(
|
|
35604
|
+
rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
|
|
35605
|
+
rangesToCompute.addMany(arrayFormulasPositions);
|
|
35606
|
+
rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
|
|
35607
|
+
this.evaluate(rangesToCompute);
|
|
35329
35608
|
console.debug("evaluate Cells", performance.now() - start, "ms");
|
|
35330
35609
|
}
|
|
35331
35610
|
getArrayFormulasImpactedByChangesOf(positions) {
|
|
35332
|
-
const
|
|
35611
|
+
const impactedRanges = new RangeSet();
|
|
35333
35612
|
for (const position of positions) {
|
|
35334
35613
|
const content = this.getters.getCell(position)?.content;
|
|
35335
35614
|
const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
|
|
35336
35615
|
if (arrayFormulaPosition !== undefined) {
|
|
35337
35616
|
// take into account new collisions.
|
|
35338
|
-
|
|
35617
|
+
impactedRanges.addPosition(arrayFormulaPosition);
|
|
35339
35618
|
}
|
|
35340
35619
|
if (!content) {
|
|
35341
35620
|
// The previous content could have blocked some array formulas
|
|
35342
|
-
|
|
35621
|
+
impactedRanges.addPosition(position);
|
|
35343
35622
|
}
|
|
35344
35623
|
}
|
|
35345
|
-
const
|
|
35346
|
-
|
|
35347
|
-
for (const zone of zonesBySheetIds[sheetId]) {
|
|
35348
|
-
impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
35349
|
-
}
|
|
35624
|
+
for (const range of [...impactedRanges]) {
|
|
35625
|
+
impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
|
|
35350
35626
|
}
|
|
35351
|
-
return
|
|
35627
|
+
return impactedRanges;
|
|
35352
35628
|
}
|
|
35353
35629
|
buildDependencyGraph() {
|
|
35354
35630
|
this.blockedArrayFormulas = this.createEmptyPositionSet();
|
|
35355
35631
|
this.spreadingRelations = new SpreadingRelation();
|
|
35356
35632
|
this.formulaDependencies = lazy(() => {
|
|
35357
|
-
const
|
|
35358
|
-
|
|
35359
|
-
.
|
|
35360
|
-
|
|
35361
|
-
|
|
35362
|
-
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
|
|
35366
|
-
|
|
35633
|
+
const rTreeItems = [];
|
|
35634
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
35635
|
+
const cells = this.getters.getCells(sheetId);
|
|
35636
|
+
for (const cellId in cells) {
|
|
35637
|
+
const cell = cells[cellId];
|
|
35638
|
+
if (cell.isFormula) {
|
|
35639
|
+
const directDependencies = cell.compiledFormula.dependencies;
|
|
35640
|
+
for (const range of directDependencies) {
|
|
35641
|
+
if (range.invalidSheetName || range.invalidXc) {
|
|
35642
|
+
continue;
|
|
35643
|
+
}
|
|
35644
|
+
rTreeItems.push({
|
|
35645
|
+
data: {
|
|
35646
|
+
sheetId,
|
|
35647
|
+
zone: positionToZone(this.getters.getCellPosition(cellId)),
|
|
35648
|
+
},
|
|
35649
|
+
boundingBox: { sheetId: range.sheetId, zone: range.zone },
|
|
35650
|
+
});
|
|
35651
|
+
}
|
|
35652
|
+
}
|
|
35653
|
+
}
|
|
35654
|
+
}
|
|
35655
|
+
return new FormulaDependencyGraph(rTreeItems);
|
|
35367
35656
|
});
|
|
35368
35657
|
}
|
|
35369
35658
|
evaluateAllCells() {
|
|
35370
35659
|
const start = performance.now();
|
|
35371
35660
|
this.evaluatedCells = new PositionMap();
|
|
35372
|
-
|
|
35661
|
+
const ranges = [];
|
|
35662
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
35663
|
+
const zone = this.getters.getSheetZone(sheetId);
|
|
35664
|
+
ranges.push({ sheetId, zone });
|
|
35665
|
+
}
|
|
35666
|
+
this.evaluate(ranges);
|
|
35373
35667
|
console.debug("evaluate all cells", performance.now() - start, "ms");
|
|
35374
35668
|
}
|
|
35375
35669
|
evaluateFormulaResult(sheetId, formulaString) {
|
|
@@ -35393,48 +35687,47 @@ class Evaluator {
|
|
|
35393
35687
|
return handleError(error, "");
|
|
35394
35688
|
}
|
|
35395
35689
|
}
|
|
35396
|
-
getAllCells() {
|
|
35397
|
-
const positions = this.createEmptyPositionSet();
|
|
35398
|
-
positions.fillAllPositions();
|
|
35399
|
-
return positions;
|
|
35400
|
-
}
|
|
35401
35690
|
/**
|
|
35402
35691
|
* Return the position of formulas blocked by the given positions
|
|
35403
35692
|
* as well as all their dependencies.
|
|
35404
35693
|
*/
|
|
35405
35694
|
getArrayFormulasBlockedBy(sheetId, zone) {
|
|
35406
|
-
const arrayFormulaPositions =
|
|
35695
|
+
const arrayFormulaPositions = new RangeSet();
|
|
35407
35696
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
|
|
35408
|
-
arrayFormulaPositions.
|
|
35697
|
+
arrayFormulaPositions.addManyPositions(arrayFormulas);
|
|
35409
35698
|
const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
|
|
35410
35699
|
if (spilledPositions.length) {
|
|
35411
35700
|
// ignore the formula spreading on the position. Keep only the blocked ones
|
|
35412
|
-
arrayFormulaPositions.
|
|
35701
|
+
arrayFormulaPositions.deleteManyPositions(spilledPositions);
|
|
35413
35702
|
}
|
|
35414
35703
|
arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
|
|
35415
35704
|
return arrayFormulaPositions;
|
|
35416
35705
|
}
|
|
35417
|
-
|
|
35706
|
+
nextRangesToUpdate = new RangeSet();
|
|
35418
35707
|
cellsBeingComputed = new Set();
|
|
35419
35708
|
symbolsBeingComputed = new Set();
|
|
35420
|
-
evaluate(
|
|
35709
|
+
evaluate(ranges) {
|
|
35421
35710
|
this.cellsBeingComputed = new Set();
|
|
35422
|
-
this.
|
|
35711
|
+
this.nextRangesToUpdate = new RangeSet(ranges);
|
|
35423
35712
|
let currentIteration = 0;
|
|
35424
|
-
while (!this.
|
|
35713
|
+
while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
|
|
35425
35714
|
this.updateCompilationParameters();
|
|
35426
|
-
const
|
|
35427
|
-
|
|
35428
|
-
|
|
35429
|
-
|
|
35430
|
-
|
|
35431
|
-
|
|
35432
|
-
|
|
35433
|
-
|
|
35434
|
-
|
|
35435
|
-
|
|
35436
|
-
|
|
35437
|
-
|
|
35715
|
+
const ranges = [...this.nextRangesToUpdate];
|
|
35716
|
+
this.nextRangesToUpdate.clear();
|
|
35717
|
+
this.clearEvaluatedRanges(ranges);
|
|
35718
|
+
for (const range of ranges) {
|
|
35719
|
+
const { left, bottom, right, top } = range.zone;
|
|
35720
|
+
for (let col = left; col <= right; col++) {
|
|
35721
|
+
for (let row = top; row <= bottom; row++) {
|
|
35722
|
+
const position = { sheetId: range.sheetId, col, row };
|
|
35723
|
+
if (this.nextRangesToUpdate.hasPosition(position)) {
|
|
35724
|
+
continue;
|
|
35725
|
+
}
|
|
35726
|
+
const evaluatedCell = this.computeCell(position);
|
|
35727
|
+
if (evaluatedCell !== EMPTY_CELL) {
|
|
35728
|
+
this.evaluatedCells.set(position, evaluatedCell);
|
|
35729
|
+
}
|
|
35730
|
+
}
|
|
35438
35731
|
}
|
|
35439
35732
|
}
|
|
35440
35733
|
onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
|
|
@@ -35443,6 +35736,16 @@ class Evaluator {
|
|
|
35443
35736
|
console.warn("Maximum iteration reached while evaluating cells");
|
|
35444
35737
|
}
|
|
35445
35738
|
}
|
|
35739
|
+
clearEvaluatedRanges(ranges) {
|
|
35740
|
+
for (const range of ranges) {
|
|
35741
|
+
const { left, bottom, right, top } = range.zone;
|
|
35742
|
+
for (let col = left; col <= right; col++) {
|
|
35743
|
+
for (let row = top; row <= bottom; row++) {
|
|
35744
|
+
this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
|
|
35745
|
+
}
|
|
35746
|
+
}
|
|
35747
|
+
}
|
|
35748
|
+
}
|
|
35446
35749
|
computeCell(position) {
|
|
35447
35750
|
const evaluation = this.evaluatedCells.get(position);
|
|
35448
35751
|
if (evaluation) {
|
|
@@ -35515,9 +35818,9 @@ class Evaluator {
|
|
|
35515
35818
|
}
|
|
35516
35819
|
invalidatePositionsDependingOnSpread(sheetId, resultZone) {
|
|
35517
35820
|
// the result matrix is split in 2 zones to exclude the array formula position
|
|
35518
|
-
const invalidatedPositions = this.
|
|
35519
|
-
invalidatedPositions.delete({ sheetId,
|
|
35520
|
-
this.
|
|
35821
|
+
const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
|
|
35822
|
+
invalidatedPositions.delete({ sheetId, zone: resultZone });
|
|
35823
|
+
this.nextRangesToUpdate.addMany(invalidatedPositions);
|
|
35521
35824
|
}
|
|
35522
35825
|
assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
|
|
35523
35826
|
const numberOfCols = this.getters.getNumberCols(sheetId);
|
|
@@ -35592,7 +35895,7 @@ class Evaluator {
|
|
|
35592
35895
|
}
|
|
35593
35896
|
const sheetId = position.sheetId;
|
|
35594
35897
|
this.invalidatePositionsDependingOnSpread(sheetId, zone);
|
|
35595
|
-
this.
|
|
35898
|
+
this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
35596
35899
|
}
|
|
35597
35900
|
/**
|
|
35598
35901
|
* Wraps a GetSymbolValue function to add cycle detection
|
|
@@ -35627,13 +35930,8 @@ class Evaluator {
|
|
|
35627
35930
|
}
|
|
35628
35931
|
return cell.compiledFormula.dependencies;
|
|
35629
35932
|
}
|
|
35630
|
-
getCellsDependingOn(
|
|
35631
|
-
|
|
35632
|
-
const zonesBySheetIds = aggregatePositionsToZones(positions);
|
|
35633
|
-
for (const sheetId in zonesBySheetIds) {
|
|
35634
|
-
ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
|
|
35635
|
-
}
|
|
35636
|
-
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
|
|
35933
|
+
getCellsDependingOn(ranges) {
|
|
35934
|
+
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
|
|
35637
35935
|
}
|
|
35638
35936
|
}
|
|
35639
35937
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40133,18 +40431,8 @@ class PivotUIPlugin extends CoreViewPlugin {
|
|
|
40133
40431
|
return EMPTY_PIVOT_CELL;
|
|
40134
40432
|
}
|
|
40135
40433
|
if (functionName === "PIVOT") {
|
|
40136
|
-
const
|
|
40137
|
-
const
|
|
40138
|
-
const includeColumnHeaders = toScalar(args[3]);
|
|
40139
|
-
const includeMeasures = toScalar(args[5]);
|
|
40140
|
-
const shouldIncludeMeasures = includeMeasures === undefined ? true : toBoolean(includeMeasures);
|
|
40141
|
-
const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
|
|
40142
|
-
const visibilityOptions = {
|
|
40143
|
-
displayColumnHeaders: shouldIncludeColumnHeaders,
|
|
40144
|
-
displayTotals: shouldIncludeTotal,
|
|
40145
|
-
displayMeasuresRow: shouldIncludeMeasures,
|
|
40146
|
-
};
|
|
40147
|
-
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
|
|
40434
|
+
const pivotStyle = getPivotStyleFromFnArgs(this.getters.getPivotCoreDefinition(pivotId), toScalar(args[1]), toScalar(args[2]), toScalar(args[3]), toScalar(args[4]), toScalar(args[5]), this.getters.getLocale());
|
|
40435
|
+
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
|
|
40148
40436
|
const pivotCol = position.col - mainPosition.col;
|
|
40149
40437
|
const pivotRow = position.row - mainPosition.row;
|
|
40150
40438
|
return pivotCells[pivotCol][pivotRow];
|
|
@@ -49874,7 +50162,6 @@ class Model extends EventBus {
|
|
|
49874
50162
|
const startSnapshot = performance.now();
|
|
49875
50163
|
console.debug("Snapshot requested");
|
|
49876
50164
|
this.session.snapshot(this.exportData());
|
|
49877
|
-
this.garbageCollectExternalResources();
|
|
49878
50165
|
console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
|
|
49879
50166
|
}
|
|
49880
50167
|
console.debug("Model created in", performance.now() - start, "ms");
|
|
@@ -50260,11 +50547,6 @@ class Model extends EventBus {
|
|
|
50260
50547
|
data = deepCopy(data);
|
|
50261
50548
|
return getXLSX(data);
|
|
50262
50549
|
}
|
|
50263
|
-
garbageCollectExternalResources() {
|
|
50264
|
-
for (const plugin of this.corePlugins) {
|
|
50265
|
-
plugin.garbageCollectExternalResources();
|
|
50266
|
-
}
|
|
50267
|
-
}
|
|
50268
50550
|
}
|
|
50269
50551
|
function createCommand(type, payload = {}) {
|
|
50270
50552
|
const command = deepCopy(payload);
|
|
@@ -50300,5 +50582,5 @@ export { BadExpressionError, BasePlugin, CellErrorType, CircularDependencyError,
|
|
|
50300
50582
|
|
|
50301
50583
|
|
|
50302
50584
|
__info__.version = "19.1.0-alpha.3";
|
|
50303
|
-
__info__.date = "2025-10-
|
|
50304
|
-
__info__.hash = "
|
|
50585
|
+
__info__.date = "2025-10-23T08:19:27.355Z";
|
|
50586
|
+
__info__.hash = "78717d4";
|