hyperformula
Version:
HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas
200 lines • 7.71 kB
JavaScript
/**
* @license
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/
import { simpleCellRange } from "../AbsoluteCellRange.mjs";
import { simpleCellAddress } from "../Cell.mjs";
import { CellAddress } from "./CellAddress.mjs";
import { ColumnAddress } from "./ColumnAddress.mjs";
import { ABSOLUTE_OPERATOR, RANGE_OPERATOR, SHEET_NAME_PATTERN, UNQUOTED_SHEET_NAME_PATTERN } from "./parser-consts.mjs";
import { RowAddress } from "./RowAddress.mjs";
const addressRegex = new RegExp(`^(${SHEET_NAME_PATTERN})?(\\${ABSOLUTE_OPERATOR}?)([A-Za-z]+)(\\${ABSOLUTE_OPERATOR}?)([0-9]+)$`);
const columnRegex = new RegExp(`^(${SHEET_NAME_PATTERN})?(\\${ABSOLUTE_OPERATOR}?)([A-Za-z]+)$`);
const rowRegex = new RegExp(`^(${SHEET_NAME_PATTERN})?(\\${ABSOLUTE_OPERATOR}?)([0-9]+)$`);
const simpleSheetNameRegex = new RegExp(`^${UNQUOTED_SHEET_NAME_PATTERN}$`);
/**
* Computes R0C0 representation of cell address based on it's string representation and base address.
*
* @param sheetMapping - mapping function needed to change name of a sheet to index
* @param stringAddress - string representation of cell address, e.g., 'C64'
* @param baseAddress - base address for R0C0 conversion
* @returns object representation of address
*/
export const cellAddressFromString = (sheetMapping, stringAddress, baseAddress) => {
const result = addressRegex.exec(stringAddress);
const col = columnLabelToIndex(result[6]);
let sheet = extractSheetNumber(result, sheetMapping);
if (sheet === undefined) {
return undefined;
}
if (sheet === null) {
sheet = undefined;
}
const row = Number(result[8]) - 1;
if (result[5] === ABSOLUTE_OPERATOR && result[7] === ABSOLUTE_OPERATOR) {
return CellAddress.absolute(col, row, sheet);
} else if (result[5] === ABSOLUTE_OPERATOR) {
return CellAddress.absoluteCol(col, row - baseAddress.row, sheet);
} else if (result[7] === ABSOLUTE_OPERATOR) {
return CellAddress.absoluteRow(col - baseAddress.col, row, sheet);
} else {
return CellAddress.relative(col - baseAddress.col, row - baseAddress.row, sheet);
}
};
export const columnAddressFromString = (sheetMapping, stringAddress, baseAddress) => {
const result = columnRegex.exec(stringAddress);
let sheet = extractSheetNumber(result, sheetMapping);
if (sheet === undefined) {
return undefined;
}
if (sheet === null) {
sheet = undefined;
}
const col = columnLabelToIndex(result[6]);
if (result[5] === ABSOLUTE_OPERATOR) {
return ColumnAddress.absolute(col, sheet);
} else {
return ColumnAddress.relative(col - baseAddress.col, sheet);
}
};
export const rowAddressFromString = (sheetMapping, stringAddress, baseAddress) => {
const result = rowRegex.exec(stringAddress);
let sheet = extractSheetNumber(result, sheetMapping);
if (sheet === undefined) {
return undefined;
}
if (sheet === null) {
sheet = undefined;
}
const row = Number(result[6]) - 1;
if (result[5] === ABSOLUTE_OPERATOR) {
return RowAddress.absolute(row, sheet);
} else {
return RowAddress.relative(row - baseAddress.row, sheet);
}
};
/**
* Computes simple (absolute) address of a cell address based on its string representation.
* - If sheet name is present in the string representation but is not present in sheet mapping, returns `undefined`.
* - If sheet name is not present in the string representation, returns {@param contextSheetId} as sheet number.
*
* @param sheetMapping - mapping function needed to change name of a sheet to index
* @param stringAddress - string representation of cell address, e.g., 'C64'
* @param contextSheetId - sheet in context of which we should parse the address
* @returns absolute representation of address, e.g., { sheet: 0, col: 1, row: 1 }
*/
export const simpleCellAddressFromString = (sheetMapping, stringAddress, contextSheetId) => {
const regExpExecArray = addressRegex.exec(stringAddress);
if (!regExpExecArray) {
return undefined;
}
const col = columnLabelToIndex(regExpExecArray[6]);
let sheet = extractSheetNumber(regExpExecArray, sheetMapping);
if (sheet === undefined) {
return undefined;
}
if (sheet === null) {
sheet = contextSheetId;
}
const row = Number(regExpExecArray[8]) - 1;
return simpleCellAddress(sheet, col, row);
};
export const simpleCellRangeFromString = (sheetMapping, stringAddress, contextSheetId) => {
const split = stringAddress.split(RANGE_OPERATOR);
if (split.length !== 2) {
return undefined;
}
const [startString, endString] = split;
const start = simpleCellAddressFromString(sheetMapping, startString, contextSheetId);
if (start === undefined) {
return undefined;
}
const end = simpleCellAddressFromString(sheetMapping, endString, start.sheet);
if (end === undefined) {
return undefined;
}
if (start.sheet !== end.sheet) {
return undefined;
}
return simpleCellRange(start, end);
};
/**
* Returns string representation of absolute address
* If sheet index is not present in sheet mapping, returns undefined
*
* @param sheetIndexMapping - mapping function needed to change sheet index to sheet name
* @param address - object representation of absolute address
* @param sheetIndex - if is not equal with address sheet index, string representation will contain sheet name
*/
export const simpleCellAddressToString = (sheetIndexMapping, address, sheetIndex) => {
const column = columnIndexToLabel(address.col);
const sheetName = sheetIndexToString(address.sheet, sheetIndexMapping);
if (sheetName === undefined) {
return undefined;
}
if (sheetIndex !== address.sheet) {
return `${sheetName}!${column}${address.row + 1}`;
} else {
return `${column}${address.row + 1}`;
}
};
export const simpleCellRangeToString = (sheetIndexMapping, address, sheetIndex) => {
const startString = simpleCellAddressToString(sheetIndexMapping, address.start, sheetIndex);
const endString = simpleCellAddressToString(sheetIndexMapping, address.end, address.start.sheet);
if (startString === undefined || endString === undefined) {
return undefined;
} else {
return `${startString}${RANGE_OPERATOR}${endString}`;
}
};
/**
* Convert column label to index
*
* @param columnStringRepresentation - column label (e.g., 'AAB')
* @returns column index
*/
function columnLabelToIndex(columnStringRepresentation) {
if (columnStringRepresentation.length === 1) {
return columnStringRepresentation.toUpperCase().charCodeAt(0) - 65;
} else {
return columnStringRepresentation.split('').reduce((currentColumn, nextLetter) => {
return currentColumn * 26 + (nextLetter.toUpperCase().charCodeAt(0) - 64);
}, 0) - 1;
}
}
/**
* Converts column index to label
*
* @param column - address to convert
* @returns string representation, e.g., 'AAB'
*/
export function columnIndexToLabel(column) {
let result = '';
while (column >= 0) {
result = String.fromCharCode(column % 26 + 97) + result;
column = Math.floor(column / 26) - 1;
}
return result.toUpperCase();
}
export function sheetIndexToString(sheetId, sheetMappingFn) {
let sheetName = sheetMappingFn(sheetId);
if (sheetName === undefined) {
return undefined;
}
if (simpleSheetNameRegex.test(sheetName)) {
return sheetName;
} else {
sheetName = sheetName.replace(/'/g, "''");
return `'${sheetName}'`;
}
}
function extractSheetNumber(regexResult, sheetMapping) {
var _a;
let maybeSheetName = (_a = regexResult[3]) !== null && _a !== void 0 ? _a : regexResult[2];
if (maybeSheetName) {
maybeSheetName = maybeSheetName.replace(/''/g, "'");
return sheetMapping(maybeSheetName);
} else {
return null;
}
}