/** * Workbook - Cross-platform Excel Workbook (Browser Version) * * Base implementation with all platform-agnostic functionality. * Node.js version (workbook.ts) extends this with file system support for CSV. * * Features: * - xlsx: File/stream/buffer support (file operations Node.js only) * - csv: CSV read/write support (file operations Node.js only) * - streaming: createStreamWriter/createStreamReader for large files */ import type { CsvParseOptions, CsvFormatOptions } from "../csv/types.js"; import type { ChartEntry, ChartExEntry } from "./chart/chart.js"; import { Chartsheet, type AddChartsheetOptions, type AddPivotChartsheetOptions } from "./chartsheet.js"; import { DefinedNames, type DefinedNameModel } from "./defined-names.js"; import type { PivotTable } from "./pivot-table.js"; import { WorkbookReader, type WorkbookReaderOptions, type CommonInput } from "./stream/workbook-reader.browser.js"; import { WorkbookWriter, type WorkbookWriterOptions } from "./stream/workbook-writer.browser.js"; import type { AddWorksheetOptions, CalculationProperties, CellValue, Font, ImageData, ThreadedCommentPerson, WorkbookProperties, WorkbookProtection, WorkbookView, Buffer as ExcelBuffer } from "./types.js"; import { Worksheet, type WorksheetModel } from "./worksheet.js"; import type { ChartsheetModel } from "./xlsx/xform/sheet/chartsheet-xform.js"; import { XLSX } from "./xlsx/xlsx.browser.js"; import type { SyntaxProbe } from "../formula/default-syntax-probe.js"; import type { MarkdownOptions } from "../markdown/types.js"; import type { IReadable, IWritable } from "../stream/types.js"; import { type DateFormat } from "../../utils/datetime.js"; /** Internal media type - more flexible than public Media type */ export interface WorkbookMedia { type: string; extension: string; filename?: string; buffer?: ExcelBuffer | Uint8Array; base64?: string; name?: string; /** External link target — when set, the image is referenced, not embedded. */ link?: string; /** * Media index of the SVG companion for this raster image. When set, the * picture is written as a raster `a:blip` plus an `asvg:svgBlip` extension * referencing the SVG media at this index. Internal bookkeeping only. */ svgMediaId?: number; } /** Internal model type for serialization */ export interface WorkbookModel { creator?: string; lastModifiedBy?: string; lastPrinted?: Date; created: Date; modified: Date; properties: Partial; protection?: WorkbookProtectionModel; worksheets: WorksheetModel[]; sheets?: WorksheetModel[]; definedNames: DefinedNameModel[]; /** * Live `DefinedNames` instance — used by the write-time chartEx * transform (`prepareChartExSidecars`) which registers hidden * `_xlchart.vN.M` defined names on the fly and needs an object * it can mutate in place. The serialised `definedNames` array * above is re-materialised from this instance after the * transform runs. Optional because the model is also used for * input paths that don't carry the live instance. */ definedNamesInstance?: unknown; views: WorkbookView[]; company: string; manager: string; title: string; subject: string; keywords: string; category: string; description: string; language?: string; revision?: number; contentStatus?: string; themes?: unknown; media: WorkbookMedia[]; pivotTables: PivotTable[]; /** Loaded pivot tables from file - used during reconciliation */ loadedPivotTables?: PivotTable[]; calcProperties: Partial; /** Default font preserved from the original file for round-trip fidelity */ defaultFont?: Partial; /** Chart entries indexed by 1-based chart number */ chartEntries?: Record; /** Chart rels indexed by chart number — preserved for round-trip */ chartRels?: Record; /** Chart style XML raw bytes indexed by style number — preserved for round-trip */ chartStyles?: Record; /** Chart colors XML raw bytes indexed by colors number — preserved for round-trip */ chartColors?: Record; chartExStyles?: Record; chartExColors?: Record; /** ChartEx raw bytes (Office 2016+ extended charts) indexed by chartEx number */ chartExEntries?: Record; /** ChartEx rels indexed by chartEx number */ chartExRels?: Record; /** Structured chartEx entries (loaded or programmatically built) indexed by chartEx number */ chartExStructuredEntries?: Record; /** Chartsheets parsed from the XLSX file — preserved for round-trip */ chartsheets?: any[]; /** * Office 365 threaded-comment person directory, hydrated from * `xl/persons/person.xml` on load and serialised back on save when * non-empty. See {@link Workbook.persons}. */ persons?: ThreadedCommentPerson[]; /** * Raw-passthrough slicer parts keyed by zip-relative path. Excelts * does not structurally model slicers yet but preserves the bytes on * round-trip so dashboards continue to work. */ slicerParts?: Record; slicerCacheParts?: Record; timelineParts?: Record; timelineCacheParts?: Record; /** * External workbook references in declaration order. Matches the on-disk * `[N]Sheet!Ref` indexing (1-based). Empty or undefined when the workbook * has no external references. */ externalLinks?: ExternalLinkModel[]; } /** Internal model for workbook-level protection (serialized to ) */ export interface WorkbookProtectionModel { lockStructure?: boolean; lockWindows?: boolean; lockRevision?: boolean; workbookPassword?: string; revisionsPassword?: string; algorithmName?: string; hashValue?: string; saltValue?: string; spinCount?: number; } /** * Cached values for a single sheet of an external workbook. Keys are the * A1-notation cell addresses *in uppercase* (e.g. `"A1"`, `"B12"`). Values * are the cached primitives Excel displays when the external file is not * currently available — must be JSON primitives: string, number, boolean, or * null for an explicitly blank cell. */ export type ExternalLinkCachedSheet = Record; /** * A single external workbook reference. Each entry corresponds to one * `xl/externalLinks/externalLink{N}.xml` part in the output file, and to * one `` entry in `xl/workbook.xml`. * * The on-disk formula syntax for referring to this workbook is `[N]Sheet!A1` * where `N` is the 1-based `index` below. */ export interface ExternalLinkModel { /** * The 1-based index used in `[N]Sheet!A1` formulas. This is the position * in the workbook's `` list (in declaration order). * Assigned automatically on read/write; treat as read-only when produced * by the library. */ index: number; /** * The rel Target that will be written into * `xl/externalLinks/_rels/externalLink{N}.xml.rels`. For relative paths * (which is what users almost always want), pass the bare filename or a * path relative to the current workbook: `"测试.xlsx"`, `"data/ref.xlsx"`. * Office resolves bare relative paths from the current workbook's * directory — *that* is the fix for the "Office goes to the Documents * folder" problem with external links. * * Absolute `file:///` or `http(s)://` URIs are accepted and written * through unchanged. */ target: string; /** * Almost always `"External"`. `"Internal"` is for embedded workbooks * (rare) and is preserved on round-trip when present in the source file. */ targetMode: "External" | "Internal"; /** * The relationship id inside `xl/_rels/workbook.xml.rels` pointing to this * external link's XML part. Populated automatically on read and * re-assigned on write. Callers should leave this undefined. */ rId?: string; /** * The sheet names exposed by the external workbook, in declaration order. * Excel writes one `` per entry under * `` inside the externalLink part. * * At minimum you must declare every sheet that appears in a formula * targeting this external workbook, otherwise Excel will fail to link * the cached values and show `#REF!`. */ sheetNames: string[]; /** * Cached primitive values per sheet. Key is the *sheet name* (matching an * entry in `sheetNames`), value is a map from A1 address to primitive. * * Cached values are what Excel displays when the referenced external file * is not available (e.g. freshly-downloaded workbook on another machine). * Writing them turns your file from "opens with errors" into "opens, * shows values, offers to update links". */ cachedValues?: Record; } /** * Supported input types for CSV parsing */ export type CsvInput = string | ArrayBuffer | Uint8Array | File | Blob | IReadable; /** * Parse options from CsvParseOptions that are exposed in CsvOptions. * Internal fields like objectMode, transform, validate, chunk, etc. are excluded. */ type CsvOptionsParseFields = Pick; /** * Format options from CsvFormatOptions that are exposed in CsvOptions. */ type CsvOptionsFormatFields = Pick; /** * CsvOptions-specific fields not present in CsvParseOptions or CsvFormatOptions. */ interface CsvOptionsExtras { sheetName?: string; sheetId?: number; /** * Append mode - when true, data is appended to existing file. * Header row is automatically skipped in append mode. * If file doesn't exist, it will be created (with headers if configured). * @default false */ append?: boolean; dateFormats?: readonly DateFormat[]; dateFormat?: string; dateUTC?: boolean; /** * Transform each cell value as rows are parsed from CSV or formatted for * CSV output. * * - During parse: `value` is the raw JS value produced by the CSV parser * (string, number, boolean, Date, ...) and already narrows to `CellValue`. * - During format: `value` is the worksheet cell's `CellValue`. * * The function should return a `CellValue`; returning non-`CellValue` * types (functions, symbols, ...) is unsupported and will break downstream * serialization. */ map?(value: CellValue, index: number): CellValue; includeEmptyRows?: boolean; requestHeaders?: Record; requestBody?: NonNullable; withCredentials?: boolean; signal?: AbortSignal; encoding?: string; onProgress?: (loaded: number, total: number) => void; stream?: boolean; highWaterMark?: number; } /** * Unified CSV options for both parsing and formatting */ export interface CsvOptions extends CsvOptionsParseFields, CsvOptionsFormatFields, CsvOptionsExtras { } declare class Workbook { /** * Streaming workbook writer class for large files. * @example * // Node.js: new Workbook.Writer({ filename: "large.xlsx" }) * // Browser: new Workbook.Writer({ stream: writableStream }) */ static Writer: typeof WorkbookWriter; /** * Streaming workbook reader class for large files. * @example * // Node.js: new Workbook.Reader("large.xlsx") * // Browser: new Workbook.Reader(readableStream) */ static Reader: typeof WorkbookReader; category: string; company: string; created: Date; description: string; keywords: string; manager: string; modified: Date; subject: string; title: string; creator?: string; lastModifiedBy?: string; lastPrinted?: Date; language?: string; revision?: number; contentStatus?: string; properties: Partial; calcProperties: Partial; views: WorkbookView[]; media: WorkbookMedia[]; pivotTables: PivotTable[]; protection?: WorkbookProtectionModel; /** * External workbook references, in declaration order. The 1-based index * of each entry matches the `[N]` prefix used inside formula strings * (e.g. the first entry is referenced as `[1]Sheet1!A1` on disk). * * Prefer {@link addExternalLink} for appending — it handles index * assignment and sheet-name deduplication. Direct mutation of this array * is supported but callers must keep indices contiguous starting at 1. */ externalLinks: ExternalLinkModel[]; protected _worksheets: Worksheet[]; protected _definedNames: DefinedNames; protected _themes?: unknown; /** Default font preserved from original file for round-trip fidelity */ protected _defaultFont?: Partial; /** * Cache of external-workbook references auto-discovered from formula * strings during previous `writeBuffer()` calls. This is an internal * stash used to keep subsequent writes fixed-point stable: once a * formula has been normalised to `[N]Sheet!A1`, the writer needs the * corresponding link metadata on the next write too, but we don't want * those auto-discovered entries to appear on the user-facing * `externalLinks` list. Indexed by lower-cased target path. * * Entries explicitly added via `addExternalLink()` live on `externalLinks` * instead — the writer combines both at serialisation time. */ protected _writerExternalLinkCache: Map; /** Global registry of table names (lowercase) for cross-worksheet uniqueness checks. */ readonly _tableNames: Set; /** Chart entries indexed by 1-based chart number */ protected _chartEntries: Record; /** Chart rels indexed by chart number — preserved for round-trip */ protected _chartRels: Record; /** Chart style XML raw bytes indexed by style number — preserved for round-trip */ protected _chartStyles: Record; /** Chart colors XML raw bytes indexed by colors number — preserved for round-trip */ protected _chartColors: Record; protected _chartExStyles: Record; protected _chartExColors: Record; /** ChartEx raw bytes (Office 2016+ extended charts) indexed by chartEx number */ protected _chartExEntries: Record; /** ChartEx rels indexed by chartEx number */ protected _chartExRels: Record; /** ChartEx structured entries (loaded or built programmatically via addChartEx). */ protected _chartExStructuredEntries: Record; /** Chartsheets parsed from the XLSX file — preserved for round-trip */ protected _chartsheets: ChartsheetModel[]; /** * Office 365 threaded-comment person directory (`xl/persons/person.xml`). * Referenced by per-sheet `threadedComment/@personId`. Hydrated on * load, preserved across save. Programmatic comments that don't supply * a personId get auto-registered against this list. */ protected _persons: ThreadedCommentPerson[]; /** * Raw XML passthrough for Office 2010+ slicers and timelines. * * Structured creation of these controls is out of scope for the * current release — the OOXML surface is large (four coordinated * part families: `xl/slicers`, `xl/slicerCaches`, `xl/timelines`, * `xl/timelineCaches`, plus sheet-level extensions and workbook- * level cache list entries). The passthrough here prevents silent * data loss when an Excel dashboard with slicers or timelines * travels through excelts: we capture every part verbatim on read * and re-emit them on write, along with their rels and Content * Types overrides. * * The map key is the full zip-relative path (e.g. * `"xl/slicers/slicer1.xml"`), the value is the exact bytes we * found in the input. Loaders that construct a workbook * programmatically never touch these maps. */ protected _slicerParts: Record; protected _slicerCacheParts: Record; protected _timelineParts: Record; protected _timelineCacheParts: Record; private _xlsx?; /** * @param options Optional construction options. * - `formulaSyntaxProbe`: An explicit tokenizer+parser probe used to * classify defined-name text during XLSX load. Providing this makes * classification deterministic for *this* workbook regardless of * whether `installFormulaEngine()` has been called. Most callers * don't need it — `installFormulaEngine()` registers a * process-wide default probe that is picked up automatically. */ constructor(options?: { formulaSyntaxProbe?: SyntaxProbe; }); /** * The default font for the workbook (fontId=0 / "Normal" style). * Cells without explicit font styles will inherit this font in Excel. * * @example * ```ts * wb.defaultFont = { name: "Arial", size: 12 }; * ``` * * When reading an existing XLSX file, this preserves the original default font * for round-trip fidelity. Setting it on a new workbook changes the default * from Calibri 11 to your chosen font. */ get defaultFont(): Partial | undefined; set defaultFont(font: Partial | undefined); /** * Import a worksheet from another workbook (or a standalone worksheet). * Deep-copies all worksheet properties via the model getter/setter, including * cell values, styles, merges, row heights, column widths, data validations, * conditional formatting, images, views, page setup, auto filter, tables, * sheet protection, page breaks, and drawing data. * * @param source - The worksheet to import * @param name - Optional name for the new worksheet (defaults to source name) * @returns The newly created worksheet */ importSheet(source: Worksheet, name?: string): Worksheet; /** * Protect the workbook structure with an optional password. * Prevents users from adding, deleting, renaming, moving, or copying worksheets. * * @param password - Optional password to protect the structure * @param options - Optional protection flags (lockStructure, lockWindows, lockRevision) */ protect(password?: string, options?: Partial): Promise; /** * Remove workbook structure protection. */ unprotect(): void; /** * xlsx file format operations * Node.js: readFile, writeFile, read (stream), write (stream), load (buffer), writeBuffer * Browser: load (buffer), writeBuffer */ get xlsx(): XLSX; /** * Read CSV from any supported input source and add as worksheet * * @example * ```ts * // String * await workbook.readCsv("a,b,c\n1,2,3"); * * // URL * await workbook.readCsv("https://example.com/data.csv"); * * // File (browser) * await workbook.readCsv(fileInput.files[0]); * * // Stream * await workbook.readCsv(readableStream); * * // With options * await workbook.readCsv(input, { delimiter: ";", sheetName: "Data" }); * ``` */ readCsv(input: CsvInput, options?: CsvOptions): Promise; /** * Write worksheet to CSV * * @example * ```ts * // Return CSV string * const csvString = workbook.writeCsv(); * const csvString = workbook.writeCsv({ delimiter: ";", sheetName: "Data" }); * * // Write to stream * await workbook.writeCsv(outputStream); * await workbook.writeCsv(outputStream, { sheetId: 1 }); * ``` */ writeCsv(options?: CsvOptions): string; writeCsv(stream: IWritable, options?: CsvOptions): Promise; /** * Write worksheet to CSV buffer (Uint8Array) * * @example * ```ts * const buffer = await workbook.writeCsvBuffer(); * const buffer = await workbook.writeCsvBuffer({ delimiter: ";", sheetName: "Data" }); * ``` */ writeCsvBuffer(options?: CsvOptions): Promise; /** * Read CSV from file (Node.js only - throws in browser) */ readCsvFile(_filename: string, _options?: CsvOptions): Promise; /** * Write CSV to file (Node.js only - throws in browser) */ writeCsvFile(_filename: string, _options?: CsvOptions): Promise; /** * Create a readable stream that outputs CSV data * * @example * ```ts * const csvStream = workbook.createCsvReadStream(); * csvStream.pipe(response); // pipe to HTTP response * ``` */ createCsvReadStream(options?: CsvOptions): IReadable; /** * Create a writable stream that accepts CSV data and adds to worksheet * * @example * ```ts * const csvStream = workbook.createCsvWriteStream({ sheetName: "Data" }); * inputStream.pipe(csvStream); * ``` */ createCsvWriteStream(options?: CsvOptions): IWritable; private _readCsvUrl; private _readCsvFile; private _readCsvBlob; private _writeCsvString; /** * Populate a worksheet from a parsed Markdown table result. * Shared by readMarkdown and readMarkdownAll. */ private _populateMarkdownWorksheet; /** * Read a Markdown table and add as worksheet. * * @example * ```ts * // From a Markdown string * workbook.readMarkdown("| Name | Age |\n| --- | --- |\n| Alice | 30 |"); * * // With options * workbook.readMarkdown(markdownString, { sheetName: "Data", map: (v, col) => Number(v) || v }); * ``` */ readMarkdown(input: string, options?: MarkdownOptions): Worksheet; /** * Read all Markdown tables from a document, each becoming a separate worksheet. * * @param input - Markdown string containing one or more tables * @param options - Parse options (sheetName is used as prefix: "sheetName", "sheetName_2", ...) * @returns Array of created worksheets (empty if no tables found) * * @example * ```ts * // Parse a document with multiple tables * const sheets = workbook.readMarkdownAll(markdownDoc); * console.log(`Created ${sheets.length} worksheets`); * * // With a naming prefix * const sheets = workbook.readMarkdownAll(markdownDoc, { sheetName: "Table" }); * // Creates "Table", "Table_2", "Table_3", ... * ``` */ readMarkdownAll(input: string, options?: MarkdownOptions): Worksheet[]; /** * Write worksheet as a Markdown table string. * * @example * ```ts * // Write first worksheet * const markdownText = workbook.writeMarkdown(); * * // Write specific worksheet with options * const markdownText = workbook.writeMarkdown({ sheetName: "Data", padding: true }); * ``` */ writeMarkdown(options?: MarkdownOptions): string; /** * Write worksheet to Markdown buffer (Uint8Array). * * @example * ```ts * const buffer = workbook.writeMarkdownBuffer(); * ``` */ writeMarkdownBuffer(options?: MarkdownOptions): Uint8Array; /** * Read Markdown from file (Node.js only - throws in browser) */ readMarkdownFile(_filename: string, _options?: MarkdownOptions): Promise; /** * Read all Markdown tables from file (Node.js only - throws in browser) */ readMarkdownAllFile(_filename: string, _options?: MarkdownOptions): Promise; /** * Write Markdown to file (Node.js only - throws in browser) */ writeMarkdownFile(_filename: string, _options?: MarkdownOptions): Promise; /** * Create a streaming workbook writer for large files. * This is more memory-efficient than using Workbook for large datasets. * * File-path output (`{ filename }`) is a Node.js-only feature and is * exposed by the Node `Workbook` subclass, which overrides this * factory to return the Node `WorkbookWriter`. This browser base * only accepts `{ stream }`. * * @example * ```ts * // Browser (or Node.js with an explicit stream) * const writer = Workbook.createStreamWriter({ stream: writableStream }); * * const sheet = writer.addWorksheet("Sheet1"); * for (let i = 0; i < 1000000; i++) { * sheet.addRow([i, `Row ${i}`]).commit(); * } * await writer.commit(); * ``` */ static createStreamWriter(options?: WorkbookWriterOptions): WorkbookWriter; /** * Create a streaming workbook reader for large files. * This is more memory-efficient than using Workbook.xlsx.readFile for large datasets. * * File-path input (`string`) is a Node.js-only feature and is exposed * by the Node `Workbook` subclass, which overrides this factory to * return the Node `WorkbookReader`. This browser base accepts the * cross-platform `CommonInput` type * (`Uint8Array | ArrayBuffer | Readable | ReadableStream`). * * @example * ```ts * // Browser or Node.js with stream / buffer * const reader = Workbook.createStreamReader(readableStream); * * for await (const event of reader) { * if (event.eventType === "worksheet") { * const worksheet = event.value; * for await (const row of worksheet) { * console.log(row.values); * } * } * } * ``` */ static createStreamReader(input: CommonInput, options?: WorkbookReaderOptions): WorkbookReader; get nextId(): number; /** * Add a new worksheet and return a reference to it */ addWorksheet(name?: string, options?: AddWorksheetOptions): Worksheet; removeWorksheetEx(worksheet: Worksheet): void; removeWorksheet(id: number | string): void; /** * Fetch sheet by name or id */ getWorksheet(id?: number | string): Worksheet | undefined; /** * Return a clone of worksheets in order */ get worksheets(): Worksheet[]; /** * Add a chartsheet containing a single chart and return the created chartsheet. */ addChartsheet(name: string | undefined, options: AddChartsheetOptions): Chartsheet; /** * Add a chartsheet containing a classic pivot chart linked to an existing pivot table. */ addPivotChartsheet(name: string | undefined, pivotTable: PivotTable, options: AddPivotChartsheetOptions): Chartsheet; /** Return chartsheets in workbook order. */ get chartsheets(): Chartsheet[]; getChartsheet(nameOrIndex: string | number): Chartsheet | undefined; removeChartsheet(nameOrIndex: string | number): boolean; private _getChartsheetModel; renameChartsheet(nameOrIndex: string | number, name: string): boolean; copyChartsheet(nameOrIndex: string | number, name?: string): Chartsheet | undefined; replaceChartsheetChart(nameOrIndex: string | number, chart: AddChartsheetOptions["chart"]): boolean; /** * Iterate over all sheets. * * Note: `workbook.worksheets.forEach` will still work but this is better. */ eachSheet(callback: (sheet: Worksheet, id: number) => void): void; get definedNames(): DefinedNames; /** * Workbook-level directory of people referenced by threaded comments. * Mutating the returned array adds/removes entries in the persistent * state; writers emit `xl/persons/person.xml` only when this list is * non-empty. * * Most callers don't need to touch this directly — creating a * {@link ThreadedComment} through `cell.note` handles registration * automatically. */ get persons(): ThreadedCommentPerson[]; /** * Register a person in the workbook persons list and return its id. * * When an entry with the same {@link displayName} + {@link userId} * already exists, its existing id is returned so duplicate * commenters collapse onto a single entry. New entries receive a * synthesised `{GUID}` id. * * @param displayName — shown in the comment bubble author line * @param userId — optional identity-provider user id (email / SID) * @param providerId — optional provider identifier ("AD", …) */ registerPerson(displayName: string, userId?: string, providerId?: string): string; /** * Recalculate all formula cells in this workbook. * * Evaluates every formula cell using the built-in calculation engine and updates * each cell's cached `result` value in-place. Formulas are evaluated with * recursive dependency resolution, memoization, and circular reference detection. * * Call this after programmatically modifying cell values that are referenced by * formulas, to ensure formula results reflect the latest data. * * Unsupported functions preserve their original cached result if one exists. * * ## Tree-shaking note * * The formula engine ships ~200KB of code (433 Excel functions, parser, * evaluator, dependency graph, spill materialiser). To keep it out of * bundles that don't need it, the engine is registered at runtime * rather than imported by the core `Workbook` module. Call * {@link installFormulaEngine} once at startup before the first call * to this method, or a clear error will be thrown explaining what to do. * * ```ts * import { installFormulaEngine } from "@cj-tech-master/excelts/formula"; * * installFormulaEngine(); // once, at startup * * sheet.getCell("A1").value = 100; * workbook.calculateFormulas(); // now works * ``` * * Callers who prefer a zero-side-effect, tree-shakeable surface can * use the functional equivalent directly: * * ```ts * import { calculateFormulas } from "@cj-tech-master/excelts/formula"; * calculateFormulas(workbook); * ``` */ calculateFormulas(): void; /** * Per-workbook registry of user-defined functions. The formula engine * consults this map before the built-in 433-function registry, so a * registered name either adds a new function (`MYFN`) or shadows a * built-in (`IRR` → project-specific variant). * * Populated by {@link registerFunction}; read by the formula engine * when the host calls `calculateFormulas()` — see * `@formula/runtime/evaluator.ts::evaluateCall`. */ userFunctions?: Map unknown; volatile?: boolean; }>; /** * Register (or replace) a custom formula function on this workbook. * * The function becomes visible to `calculateFormulas()` on this * workbook only — the built-in registry stays untouched. Names are * case-insensitive (normalised to uppercase) and must not include * the `_XLFN.` prefix — the engine strips that automatically. * * @param name Function name (case-insensitive). * @param fn Implementation. Receives already-evaluated RuntimeValue * arguments; return a RuntimeValue. Wrap failures with * `rvError("#VALUE!")` rather than throwing — throws are * caught at the evaluator boundary and surface as * `#VALUE!` so a buggy custom function doesn't tear * down the whole calculation pass. * @param options Optional arity bounds. Defaults to `minArity=0`, * `maxArity=255` (Excel's universal argument cap), so * simple variadic functions work without extra config. * Set `volatile: true` when the function should be * re-evaluated on every calc cycle (analogous to * built-in `RAND`, `NOW`). Currently reserved for * future use; the engine recomputes every formula on * each `calculateFormulas()` call regardless. * * ```ts * import { rvNumber } from "@cj-tech-master/excelts/formula"; * workbook.registerFunction("DOUBLE", ([x]) => { * return rvNumber((x as any).value * 2); * }, { minArity: 1, maxArity: 1 }); * ``` */ registerFunction(name: string, fn: (args: unknown[]) => unknown, options?: { minArity?: number; maxArity?: number; volatile?: boolean; }): void; /** * Remove a user-registered function. No-op when the name isn't * registered; returns `true` when an entry was removed. */ unregisterFunction(name: string): boolean; clearThemes(): void; /** * Register an image with the workbook and return its numeric id. Pass the id * to {@link Worksheet.addImage}, {@link Worksheet.addBackgroundImage}, or * {@link Worksheet.addWatermark} to place it. * * The image is either **embedded** or **linked (external)**: * * - **Embedded** — supply `buffer`, `base64`, or `filename`. The bytes are * written into the `.xlsx` package (`xl/media/imageN.ext`). Self-contained, * but inflates file size. * - **Linked (external)** — supply only `link` (a URL or local file path). * No bytes are stored; the package keeps a relationship with * `TargetMode="External"` and the picture is rendered via ``. * Keeps the file small, but the image is resolved by Excel at open time. * * If both bytes and a `link` are provided, **embedding wins**. * * Linked images work with **cell pictures** ({@link Worksheet.addImage}) and * **overlay watermarks** ({@link Worksheet.addWatermark} with `mode: * "overlay"`). Worksheet background images and header/footer (VML) watermarks * cannot be linked — they require an embedded image. * * Note: Excel treats linked images as volatile — a moved/missing target * shows a broken-image placeholder, and modern Excel may not auto-load * remote URLs for security reasons. Prefer embedding for self-contained files. * * @example Embedded image * ```typescript * const id = workbook.addImage({ buffer: pngBytes, extension: "png" }); * worksheet.addImage(id, "B2:D6"); * ``` * * @example Linked (external) image — no bytes stored * ```typescript * const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" }); * worksheet.addImage(id, "B2:D6"); * ``` * * @example SVG with raster fallback — crisp in modern Excel, safe everywhere * ```typescript * const id = workbook.addImage({ * buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers * extension: "png", * svg: { buffer: svgBytes } // shown by Excel 2016+ * }); * worksheet.addImage(id, "B2:D6"); * ``` */ addImage(image: ImageData): number; getImage(id: number | string): WorkbookMedia | undefined; /** * Return the next available 1-based chart number. */ nextChartNumber(): number; /** * Store a chart entry in the workbook (keyed by chartNumber). */ addChartEntry(entry: ChartEntry): void; setChartStyle(chartNumber: number, data: Uint8Array): void; setChartColors(chartNumber: number, data: Uint8Array): void; copyChartSidecars(sourceChartNumber: number, targetChartNumber: number, targetWorkbook?: Workbook): void; /** * Copy the media referenced by `target` (e.g. `../media/image3.png`) * from this workbook's media collection into `targetWorkbook`, then * return the rewritten Target pointing at the destination workbook's * copy. Returns `undefined` when the source media can't be * resolved — callers leave the original target in place and let * the writer emit a broken rel (same degradation as before the * cross-workbook rewrite landed). * * The on-disk naming convention is determined by the destination * workbook's `addImage`, so we take the `id` the new image gets and * compute `../media/image{id+1}.{ext}` (the same formula used in * `chart-images.ts:resolvePendingChartImages`). Centralising the * mapping here keeps the cross-workbook copy robust against future * media-naming changes on either workbook. */ private _rewriteCrossWorkbookImageTarget; /** * Copy the ChartEx-specific sidecar state from one `chartExNumber` slot * to another. Classic charts have `copyChartSidecars` for chart-style / * chart-colors; ChartEx charts carry their own `_chartExRels` * (relationship entries for extension packages, embedded images, etc.) * that the classic helper does not touch. Without this, cloning a * ChartEx via {@link Chart.copyTo} / {@link Chartsheet.clone} silently * dropped every relationship the source had — authored `cx14:` * extensions, embedded custom geometry, or linked media ended up * pointing at an undefined rel id on the clone. */ copyChartExSidecars(sourceChartExNumber: number, targetChartExNumber: number, targetWorkbook?: Workbook): void; private _applyChartsheetSidecars; /** * Retrieve a chart entry by its 1-based chart number. */ getChartEntry(chartNumber: number): ChartEntry | undefined; /** * Remove a chart entry from the workbook. * Safe to call even if the chart number doesn't exist. */ removeChartEntry(chartNumber: number): void; /** Return the next available 1-based chartEx number. */ nextChartExNumber(): number; /** * Store a structured chartEx entry. * Loaded entries may also keep raw bytes for clean passthrough. */ addChartExStructuredEntry(entry: ChartExEntry): void; /** Get a structured chartEx entry by number. */ getChartExStructuredEntry(chartExNumber: number): ChartExEntry | undefined; private _nextChartsheetNo; /** * Next value for the unified `orderNo` (tab-bar position) counter * shared between worksheets and chartsheets. Used by the writer's * `prepare()` to emit `` in the author's insertion order, * preserving interleaved `[ws, cs, ws]` layouts that the old * sheetNo-based sort used to reshuffle. */ private _nextSheetOrderNo; private _nextSheetId; /** * Validate a sheet name (worksheet OR chartsheet) against Excel's * single unified namespace. Returns the (possibly truncated) name on * success; throws {@link WorksheetNameError} on invalid input. * * Unifying the check at the workbook level fixes three related * regressions that used to exist in the per-family validators: * 1. `Worksheet.name` setter only cross-checked against other * worksheets, so `addChartsheet("S")` followed by * `addWorksheet("S")` silently produced a duplicate tab name. * 2. The chartsheet regex was missing the backslash, so * `addChartsheet("A\\B")` sneaked through — Excel rejects it. * 3. `Chartsheet.name = …` bypassed validation entirely, letting * users mutate the model into a corrupt state. * * @param name - Proposed sheet name. `undefined` / empty / over-31 * chars / containing any of `* ? : \\ / [ ]` / leading or trailing * single-quote is rejected. Names ≤31 chars are passed through; * longer ones are truncated (non-production builds emit a warning). * @param existing - The sheet being renamed (if any) — it is * excluded from the duplicate check so `sheet.name = sheet.name` * is a no-op rather than a self-collision. */ validateSheetName(name: string, existing?: Worksheet | { name: string; }): string; private _validateChartsheetName; /** Remove a structured chartEx entry. */ removeChartExStructuredEntry(chartExNumber: number): void; /** * Declare that formulas in this workbook may reference an external * workbook. Registers the target so the output file contains the required * `xl/externalLinks/externalLink{N}.xml` part plus its `.rels` sibling * and Office/WPS can resolve the reference correctly. * * When Office opens the file, it resolves a relative `target` like * `"测试.xlsx"` **relative to the current workbook's directory** — which * is the exact behaviour the user expects when they write * `=[测试.xlsx]Sheet1!A1`. Absolute `file:///…` or `http(s)://…` URIs * are accepted and written through unchanged. * * @returns the registered {@link ExternalLinkModel}. Its `index` field is * the 1-based number used inside the `[N]` prefix of on-disk formula * strings (the library rewrites `[target]` forms to `[index]` at write * time automatically). * * @example * ```ts * const wb = new Workbook(); * const ws = wb.addWorksheet("Main"); * * // Declare the link once — sheet names and cached values are optional * // but improve interoperability (Excel displays cached values when the * // external file is unavailable). * wb.addExternalLink({ * target: "测试.xlsx", * sheetNames: ["Sheet1"], * cachedValues: { Sheet1: { A1: 42 } } * }); * * // Write the formula using either the target name OR the numeric index; * // the library normalises both to the on-disk `[N]` form. * ws.getCell("A1").value = { formula: "=[测试.xlsx]Sheet1!A1", result: 42 }; * ``` */ addExternalLink(input: { target: string; sheetNames?: string[]; cachedValues?: ExternalLinkModel["cachedValues"]; targetMode?: ExternalLinkModel["targetMode"]; }): ExternalLinkModel; /** * Retrieve an external link by its 1-based on-disk index (the number * inside the `[N]` formula prefix) or by matching target path. */ getExternalLink(indexOrTarget: number | string): ExternalLinkModel | undefined; get model(): WorkbookModel; set model(value: WorkbookModel); } export { Workbook };