hyperformula
Version:
HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas
245 lines • 7.49 kB
JavaScript
/**
* @license
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/
import { AbsoluteCellRange } from "../AbsoluteCellRange.mjs";
import { simpleCellAddress } from "../Cell.mjs";
/**
* Mapping from address ranges to range vertices
*/
export class RangeMapping {
constructor() {
/** Map in which actual data is stored. */
this.rangeMapping = new Map();
}
getMappingSize(sheet) {
var _a, _b;
return (_b = (_a = this.rangeMapping.get(sheet)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
}
/**
* Saves range vertex
*
* @param vertex - vertex to save
*/
setRange(vertex) {
let sheetMap = this.rangeMapping.get(vertex.getStart().sheet);
if (sheetMap === undefined) {
sheetMap = new Map();
this.rangeMapping.set(vertex.getStart().sheet, sheetMap);
}
const key = keyFromAddresses(vertex.getStart(), vertex.getEnd());
sheetMap.set(key, vertex);
}
removeRange(vertex) {
const sheet = vertex.getStart().sheet;
const sheetMap = this.rangeMapping.get(sheet);
if (sheetMap === undefined) {
return;
}
const key = keyFromAddresses(vertex.getStart(), vertex.getEnd());
sheetMap.delete(key);
if (sheetMap.size === 0) {
this.rangeMapping.delete(sheet);
}
}
/**
* Returns associated vertex for given range
*
* @param start - top-left corner of the range
* @param end - bottom-right corner of the range
*/
getRange(start, end) {
const sheetMap = this.rangeMapping.get(start.sheet);
const key = keyFromAddresses(start, end);
return sheetMap === null || sheetMap === void 0 ? void 0 : sheetMap.get(key);
}
fetchRange(start, end) {
const maybeRange = this.getRange(start, end);
if (!maybeRange) {
throw Error('Range does not exist');
}
return maybeRange;
}
truncateRanges(span, coordinate) {
const verticesToRemove = Array();
const updated = Array();
const verticesWithChangedSize = Array();
const sheet = span.sheet;
for (const [key, vertex] of this.entriesFromSheet(span.sheet)) {
const range = vertex.range;
if (span.start <= coordinate(vertex.range.end)) {
range.removeSpan(span);
if (range.shouldBeRemoved()) {
this.removeByKey(sheet, key);
verticesToRemove.push(vertex);
} else {
updated.push([key, vertex]);
}
verticesWithChangedSize.push(vertex);
}
}
const verticesToMerge = [];
updated.sort((left, right) => compareBy(left[1], right[1], coordinate));
for (const [oldKey, vertex] of updated) {
const newKey = keyFromRange(vertex.range);
if (newKey === oldKey) {
continue;
}
const existingVertex = this.getByKey(sheet, newKey);
this.removeByKey(sheet, oldKey);
if (existingVertex !== undefined && vertex != existingVertex) {
verticesToMerge.push([existingVertex, vertex]);
} else {
this.setRange(vertex);
}
}
return {
verticesToRemove,
verticesToMerge,
verticesWithChangedSize
};
}
moveAllRangesInSheetAfterRowByRows(sheet, row, numberOfRows) {
return this.updateVerticesFromSheet(sheet, (key, vertex) => {
if (row <= vertex.start.row) {
vertex.range.shiftByRows(numberOfRows);
return {
changedSize: false,
vertex: vertex
};
} else if (row > vertex.start.row && row <= vertex.end.row) {
vertex.range.expandByRows(numberOfRows);
return {
changedSize: true,
vertex: vertex
};
} else {
return undefined;
}
});
}
moveAllRangesInSheetAfterColumnByColumns(sheet, column, numberOfColumns) {
return this.updateVerticesFromSheet(sheet, (key, vertex) => {
if (column <= vertex.start.col) {
vertex.range.shiftByColumns(numberOfColumns);
return {
changedSize: false,
vertex: vertex
};
} else if (column > vertex.start.col && column <= vertex.end.col) {
vertex.range.expandByColumns(numberOfColumns);
return {
changedSize: true,
vertex: vertex
};
} else {
return undefined;
}
});
}
moveRangesInsideSourceRange(sourceRange, toRight, toBottom, toSheet) {
this.updateVerticesFromSheet(sourceRange.sheet, (key, vertex) => {
if (sourceRange.containsRange(vertex.range)) {
vertex.range.shiftByColumns(toRight);
vertex.range.shiftByRows(toBottom);
vertex.range.moveToSheet(toSheet);
return {
changedSize: false,
vertex: vertex
};
} else {
return undefined;
}
});
}
removeRangesInSheet(sheet) {
if (this.rangeMapping.has(sheet)) {
const ranges = this.rangeMapping.get(sheet).values();
this.rangeMapping.delete(sheet);
return ranges;
}
return [][Symbol.iterator]();
}
*rangesInSheet(sheet) {
const sheetMap = this.rangeMapping.get(sheet);
if (!sheetMap) {
return;
}
yield* sheetMap.values();
}
*rangeVerticesContainedInRange(sourceRange) {
for (const rangeVertex of this.rangesInSheet(sourceRange.sheet)) {
if (sourceRange.containsRange(rangeVertex.range)) {
yield rangeVertex;
}
}
}
/**
* Finds smaller range does have own vertex.
*
* @param range
*/
findSmallerRange(range) {
if (range.height() > 1 && Number.isFinite(range.height())) {
const valuesRangeEndRowLess = simpleCellAddress(range.end.sheet, range.end.col, range.end.row - 1);
const rowLessVertex = this.getRange(range.start, valuesRangeEndRowLess);
if (rowLessVertex !== undefined) {
const restRange = AbsoluteCellRange.fromSimpleCellAddresses(simpleCellAddress(range.start.sheet, range.start.col, range.end.row), range.end);
return {
smallerRangeVertex: rowLessVertex,
restRange
};
}
}
return {
restRange: range
};
}
*entriesFromSheet(sheet) {
const sheetMap = this.rangeMapping.get(sheet);
if (!sheetMap) {
return;
}
yield* sheetMap.entries();
}
removeByKey(sheet, key) {
this.rangeMapping.get(sheet).delete(key);
}
getByKey(sheet, key) {
var _a;
return (_a = this.rangeMapping.get(sheet)) === null || _a === void 0 ? void 0 : _a.get(key);
}
updateVerticesFromSheet(sheet, fn) {
const updated = Array();
for (const [key, vertex] of this.entriesFromSheet(sheet)) {
const result = fn(key, vertex);
if (result !== undefined) {
this.removeByKey(sheet, key);
updated.push(result);
}
}
updated.forEach(entry => {
this.setRange(entry.vertex);
});
return {
verticesWithChangedSize: updated.filter(entry => entry.changedSize).map(entry => entry.vertex)
};
}
}
function keyFromAddresses(start, end) {
return `${start.col},${start.row},${end.col},${end.row}`;
}
function keyFromRange(range) {
return keyFromAddresses(range.start, range.end);
}
const compareBy = (left, right, coordinate) => {
const leftStart = coordinate(left.range.start);
const rightStart = coordinate(left.range.start);
if (leftStart === rightStart) {
const leftEnd = coordinate(left.range.end);
const rightEnd = coordinate(right.range.end);
return leftEnd - rightEnd;
} else {
return leftStart - rightStart;
}
};