UNPKG

hyperformula

Version:

HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas

333 lines (331 loc) 11.3 kB
"use strict"; exports.__esModule = true; exports.SheetMapping = void 0; var _errors = require("../errors"); var _i18n = require("../i18n"); /** * @license * Copyright (c) 2025 Handsoncode. All rights reserved. */ /** * Representation of a sheet internal to SheetMapping. Not exported outside of this file. */ class Sheet { constructor(id, displayName, isPlaceholder = false) { this.id = id; this.displayName = displayName; this.isPlaceholder = isPlaceholder; } /** * Returns the canonical (normalized) name of the sheet. */ get canonicalName() { return SheetMapping.canonicalizeSheetName(this.displayName); } } /** * Manages the sheets in the instance. * - Can convert between sheet names and ids and vice versa. * - Also stores placeholders for sheets that are used in formulas but not yet added. They are marked as isPlaceholder=true. * - Sheetnames thet differ only in case are considered the same. (See: canonicalizeSheetName) */ class SheetMapping { constructor(languages) { /** * Last used sheet ID. Used to generate new sheet IDs. */ this.lastSheetId = -1; /** * Mapping from canonical sheet name to sheet ID. */ this.mappingFromCanonicalNameToId = new Map(); /** * Mapping from sheet ID to sheet. */ this.allSheets = new Map(); this.sheetNamePrefix = languages.getUITranslation(_i18n.UIElement.NEW_SHEET_PREFIX); } /** * Converts sheet name to canonical/normalized form. * @static */ static canonicalizeSheetName(sheetDisplayName) { return sheetDisplayName.toLowerCase(); } /** * Returns sheet ID for the given name. By default excludes placeholders. */ getSheetId(sheetName, options = {}) { var _a; return (_a = this._getSheetByName(sheetName, options)) === null || _a === void 0 ? void 0 : _a.id; } /** * Returns sheet ID for the given name. Excludes placeholders. * * @throws {NoSheetWithNameError} if the sheet with the given name does not exist. */ getSheetIdOrThrowError(sheetName) { const sheet = this._getSheetByName(sheetName, {}); if (sheet === undefined) { throw new _errors.NoSheetWithNameError(sheetName); } return sheet.id; } /** * Returns display name for the given sheet ID. Excludes placeholders. * * @returns {Maybe<string>} the display name, or undefined if the sheet with the given ID does not exist. */ getSheetName(sheetId) { var _a; return (_a = this._getSheet(sheetId, {})) === null || _a === void 0 ? void 0 : _a.displayName; } /** * Returns display name for the given sheet ID. Excludes placeholders. * * @throws {NoSheetWithIdError} if the sheet with the given ID does not exist. */ getSheetNameOrThrowError(sheetId, options = {}) { return this._getSheetOrThrowError(sheetId, options).displayName; } /** * Iterates over all sheet display names. By default excludes placeholders. */ *iterateSheetNames(options = {}) { for (const sheet of this.allSheets.values()) { if (options.includePlaceholders || !sheet.isPlaceholder) { yield sheet.displayName; } } } /** * Returns array of all sheet display names. By default excludes placeholders. */ getSheetNames(options = {}) { return Array.from(this.iterateSheetNames(options)); } /** * Returns total count of sheets. By default excludes placeholders. */ numberOfSheets(options = {}) { return this.getSheetNames(options).length; } /** * Checks if sheet with given ID exists. By default excludes placeholders. */ hasSheetWithId(sheetId, options = {}) { return this._getSheet(sheetId, options) !== undefined; } /** * Checks if sheet with given name exists (case-insensitive). Excludes placeholders. */ hasSheetWithName(sheetName) { return this._getSheetByName(sheetName, {}) !== undefined; } /** * Adds new sheet with optional name and returns its ID. * If called with a name of an existing placeholder sheet, converts the placeholder sheet to a real sheet. * * @throws {SheetNameAlreadyTakenError} if the sheet with the given name already exists. */ addSheet(newSheetDisplayName = `${this.sheetNamePrefix}${this.lastSheetId + 2}`) { const sheetWithConflictingName = this._getSheetByName(newSheetDisplayName, { includePlaceholders: true }); if (sheetWithConflictingName) { if (!sheetWithConflictingName.isPlaceholder) { throw new _errors.SheetNameAlreadyTakenError(newSheetDisplayName); } sheetWithConflictingName.isPlaceholder = false; return sheetWithConflictingName.id; } this.lastSheetId++; const sheet = new Sheet(this.lastSheetId, newSheetDisplayName); this._storeSheetInMappings(sheet); return sheet.id; } /** * Adds a sheet with a specific ID and name. Used for redo operations. * If called with a name of an existing placeholder sheet, converts the placeholder sheet to a real sheet. * * @throws {SheetNameAlreadyTakenError} if the sheet with the given name already exists. */ addSheetWithId(sheetId, sheetDisplayName) { const sheetWithConflictingName = this._getSheetByName(sheetDisplayName, { includePlaceholders: true }); if (sheetWithConflictingName) { if (sheetWithConflictingName.id !== sheetId) { throw new _errors.SheetNameAlreadyTakenError(sheetDisplayName); } if (!sheetWithConflictingName.isPlaceholder) { throw new _errors.SheetNameAlreadyTakenError(sheetDisplayName); } sheetWithConflictingName.isPlaceholder = false; return; } if (sheetId > this.lastSheetId) { this.lastSheetId = sheetId; } const sheet = new Sheet(sheetId, sheetDisplayName); this._storeSheetInMappings(sheet); } /** * Adds a placeholder sheet with the given name if it does not exist yet */ addPlaceholderIfNotExists(sheetName) { const sheetWithConflictingName = this._getSheetByName(sheetName, { includePlaceholders: true }); if (sheetWithConflictingName) { return sheetWithConflictingName.id; } this.lastSheetId++; const sheet = new Sheet(this.lastSheetId, sheetName, true); this._storeSheetInMappings(sheet); return sheet.id; } /** * Adds a placeholder sheet with a specific ID and name. * Used for undo operations to restore previously merged placeholder sheets. * * @throws {SheetNameAlreadyTakenError} if the sheet with the given name already exists. */ addPlaceholderWithId(sheetId, sheetDisplayName) { const sheetWithConflictingName = this._getSheetByName(sheetDisplayName, { includePlaceholders: true }); if (sheetWithConflictingName) { throw new _errors.SheetNameAlreadyTakenError(sheetDisplayName); } if (this.hasSheetWithId(sheetId, { includePlaceholders: true })) { throw new Error(`Sheet with id ${sheetId} already exists`); } if (sheetId > this.lastSheetId) { this.lastSheetId = sheetId; } const sheet = new Sheet(sheetId, sheetDisplayName, true); this._storeSheetInMappings(sheet); } /** * * Removes sheet with given ID. * If sheet does not exist, does nothing. * @returns {boolean} true if sheet was removed, false if it did not exist. */ removeSheetIfExists(sheetId, options = {}) { const sheet = this._getSheet(sheetId, options); if (!sheet) { return false; } this.allSheets.delete(sheetId); this.mappingFromCanonicalNameToId.delete(sheet.canonicalName); if (sheetId === this.lastSheetId) { this.lastSheetId--; } return true; } /** * Marks sheet with given ID as a placeholder. * @throws {NoSheetWithIdError} if the sheet with the given ID does not exist */ markSheetAsPlaceholder(sheetId) { const sheet = this._getSheetOrThrowError(sheetId, {}); sheet.isPlaceholder = true; } /** * Renames sheet. * - If called with sheetId of a placeholder sheet, throws {NoSheetWithIdError}. * - If newDisplayName is conflicting with an existing sheet, throws {SheetNameAlreadyTakenError}. * - If newDisplayName is conflicting with a placeholder sheet name, deletes the placeholder sheet and returns its id as mergedWithPlaceholderSheet. * * @throws {SheetNameAlreadyTakenError} if the sheet with the given name already exists. * @throws {NoSheetWithIdError} if the sheet with the given ID does not exist. */ renameSheet(sheetId, newDisplayName) { const sheet = this._getSheetOrThrowError(sheetId, {}); const currentDisplayName = sheet.displayName; if (currentDisplayName === newDisplayName) { return { previousDisplayName: undefined }; } const sheetWithConflictingName = this._getSheetByName(newDisplayName, { includePlaceholders: true }); let mergedWithPlaceholderSheet = undefined; if (sheetWithConflictingName !== undefined && sheetWithConflictingName.id !== sheet.id) { if (!sheetWithConflictingName.isPlaceholder) { throw new _errors.SheetNameAlreadyTakenError(newDisplayName); } else { this.mappingFromCanonicalNameToId.delete(sheetWithConflictingName.canonicalName); this.allSheets.delete(sheetWithConflictingName.id); if (sheetWithConflictingName.id === this.lastSheetId) { this.lastSheetId--; } mergedWithPlaceholderSheet = sheetWithConflictingName.id; } } const currentCanonicalName = sheet.canonicalName; this.mappingFromCanonicalNameToId.delete(currentCanonicalName); sheet.displayName = newDisplayName; this._storeSheetInMappings(sheet); return { previousDisplayName: currentDisplayName, mergedWithPlaceholderSheet }; } /** * Stores sheet in both internal mappings. * - If ID exists, it is updated. If not, it is added. * - If canonical name exists, it is updated. If not, it is added. * * @internal */ _storeSheetInMappings(sheet) { this.allSheets.set(sheet.id, sheet); this.mappingFromCanonicalNameToId.set(sheet.canonicalName, sheet.id); } /** * Returns sheet by ID * * @returns {Maybe<Sheet>} the sheet, or undefined if not found. * @internal */ _getSheet(sheetId, options) { const retrievedSheet = this.allSheets.get(sheetId); if (retrievedSheet === undefined) { return undefined; } return options.includePlaceholders || !retrievedSheet.isPlaceholder ? retrievedSheet : undefined; } /** * Returns sheet by name * * @returns {Maybe<Sheet>} the sheet, or undefined if not found. * @internal */ _getSheetByName(sheetName, options) { const sheetId = this.mappingFromCanonicalNameToId.get(SheetMapping.canonicalizeSheetName(sheetName)); if (sheetId === undefined) { return undefined; } return this._getSheet(sheetId, options); } /** * Returns sheet by ID * * @throws {NoSheetWithIdError} if the sheet with the given ID does not exist. * @internal */ _getSheetOrThrowError(sheetId, options) { const sheet = this._getSheet(sheetId, options); if (sheet === undefined) { throw new _errors.NoSheetWithIdError(sheetId); } return sheet; } } exports.SheetMapping = SheetMapping;