xlvane 1.3.0 → 1.3.2

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/README.md CHANGED
@@ -104,7 +104,8 @@ In Node.js, `writeWorkbookFile(workbook, 'sales.xlsx')` from `xlvane/node` write
104
104
 
105
105
  - Values: numbers, strings, booleans, `Date`s (stored as serial numbers with a `yyyy-mm-dd` or `yyyy-mm-dd hh:mm:ss` format unless the style sets one), `{ error: '#N/A' }`, and `{ formula, result }`, also as an array formula (`{ formula, array: 'A1:A3' }`) or a shared formula (`{ formula, shared: 'B1:B9' }` on the first cell, `{ sharedFormula: 'B1' }` on the others). xlvane writes the formula text and the cached result you give; it does not calculate formulas.
106
106
  - Styles: font (name, size, bold, italic, underline, strike, colour, superscript and subscript, family, scheme, charset, outline, shadow), fill (solid colour, pattern, or linear and path gradient), borders (top, right, bottom, left, diagonal), alignment (horizontal, vertical, wrap, shrink to fit, indent, rotation, reading order), cell protection (`locked`, `hidden`) and number format. Identical styles are stored once. `sheet.style(range, style)` merges a style into every cell of a range.
107
- - Layout: column widths and hidden columns, row heights and hidden rows, outline levels of rows and columns (`row(n, { outlineLevel: 1, collapsed })`, `column('B', { outlineLevel: 1 })`), merged ranges, frozen rows and columns, autofilter, hidden and very hidden sheets. `sheet.properties({ defaultRowHeight, outlineLevelRow, outlineLevelCol, summaryBelow, summaryRight })` sets the sheet's format properties.
107
+ - Layout: column widths and hidden columns, row heights and hidden rows, outline levels of rows and columns (`row(n, { outlineLevel: 1, collapsed })`, `column('B', { outlineLevel: 1 })`), the default style of a row (`row(n, { style })`, read back as `sheet.rowInfo(n).style`; it is not applied to the row's cells), merged ranges, frozen rows and columns, autofilter, hidden and very hidden sheets. `sheet.properties({ defaultRowHeight, outlineLevelRow, outlineLevelCol, summaryBelow, summaryRight })` sets the sheet's format properties.
108
+ - Default font: `createWorkbook({ defaultFont: { name: 'Arial', size: 10 } })` sets the workbook's default font (Calibri 11 otherwise); a read workbook gives it as `workbook.defaultFont`.
108
109
  - Workbook: `workbook.views([{ x, y, width, height, firstSheet, activeTab, visibility }])` sets the workbook windows (`activeTab` and `firstSheet` are checked against the sheets when the workbook is written) and `workbook.calculation({ fullCalcOnLoad: true })` asks applications to recalculate when the file is opened. `addSheet(name, { sheetId })` sets the sheet's id in the file (1 to 65534, unique; other sheets get the lowest unused ids), and reading gives `sheet.sheetId`.
109
110
  - Writer options: `writeWorkbook(workbook, { sharedStrings: false })` writes text inline instead of in a shared string table; `{ compressionLevel: 0 }` (0–9, default 6) stores or compresses the parts. The same options work for `writeWorkbookFile` and, with `compressionLevel`, `writeWorkbookStream`.
110
111
  - Invalid input is rejected when it is set, with an `XlvError` saying what and where, for example `XLV_E_INVALID_SHEET_NAME` or `XLV_E_INVALID_STYLE` with the allowed values.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { Vt as suggest, _ as inspect, v as isoDate, x as readWorkbook, yn as parseRange, zn as XlvError } from "./options-Cxt-LJE4.js";
3
- import { a as appendRows, c as fromCSV, d as fromJSON, f as pickSheet, h as toJSON, l as inferValue, n as insertRows, o as editCells, p as readTable, s as diff, t as deleteRows, u as toCSV } from "./rows-DltZ-hiW.js";
4
- import { i as readInput } from "./node-files-SVg17pky.js";
2
+ import { Vt as suggest, _ as inspect, v as isoDate, x as readWorkbook, yn as parseRange, zn as XlvError } from "./options-BD873QNd.js";
3
+ import { a as appendRows, c as fromCSV, d as fromJSON, f as pickSheet, h as toJSON, l as inferValue, n as insertRows, o as editCells, p as readTable, s as diff, t as deleteRows, u as toCSV } from "./rows-BxGneQTL.js";
4
+ import { i as readInput } from "./node-files-BkN3L0JT.js";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { lstat, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
7
7
  import process from "node:process";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as InspectOptions, $n as SheetProtection, $t as DataValidationOperator, A as toJSON, An as SheetChart, At as Workbook, B as FormulaResult, Bn as Hyperlink, Bt as CfValue, C as TableRow, Cn as ChartInput, Cr as isXlvError, Ct as Sheet, D as fromJSON, Dn as ChartSeriesInput, Dr as DefinedNameOptions, Dt as SheetState, E as WriteTableOptions, En as ChartSeries, Er as DefinedName, Et as SheetProperties, F as CellInput, Fn as ImageOptions, Ft as TableDefinitionColumn, G as SheetPropertiesOptions, Gn as PageOrder, Gt as ConditionalFormatRule, H as NewSheet, Hn as HeaderFooter, Ht as CfValueType, I as ColumnOptions, In as ImagePositionInput, It as TableInput, J as WriteWorkbookOptions, Jn as PageSetupOptions, Jt as DataBarAxisPosition, K as SheetViewOptions, Kn as PageOrientation, Kt as ConditionalFormatRuleInput, L as CreateWorkbookOptions, Ln as SheetImage, Lt as TableStyleOptions, M as StandardSchemaV1, Mn as ImageAnchor, Mt as WorkbookView, N as AddSheetOptions, Nn as ImageAnchorInput, Nt as TableColumnInput, O as readTable, On as ChartText, Ot as SheetView, P as CalculationOptions, Pn as ImageFormat, Pt as TableDefinition, Q as InspectDefinedName, Qn as PrintTitles, Qt as DataValidationImeMode, R as ERROR_VALUES, Rn as WorkbookImage, Rt as TotalsRowFunction, S as TableResult, Sn as ChartAxisInput, Sr as XlvWarningCode, St as RowInfo, T as ToJSONOptions, Tn as ChartLegendPosition, Tr as ReadLimits, Tt as SheetLayout, U as NewWorkbook, Un as PageBreaks, Ut as ConditionalFormat, V as FreezeOptions, Vn as HyperlinkTarget, Vt as CfValueInput, W as RowOptions, Wn as PageMargins, Wt as ConditionalFormatOperator, X as writeWorkbook, Xn as PrintErrors, Xt as DataValidation, Y as createWorkbook, Yn as PrintCellComments, Yt as DataBarDirection, Z as InspectColumn, Zn as PrintOptions, Zt as DataValidationErrorStyle, _ as JsonTable, _n as ReadingOrder, _r as XlvErrorCode, _t as DateSystem, a as WriteWorkbookStreamOptions, an as BorderEdge, ar as PasswordHash, at as ReadOptions, b as TableColumnSpec, bn as VerticalAlignment, br as XlvLocation, bt as FrozenPanes, c as OpenWorkbookStreamOptions, cn as CellStyle, cr as legacyPasswordHash, ct as NoteBox, d as StreamRow, dn as FontScheme, dr as WorkbookProperties, dt as NoteMargins, en as DataValidationRule, er as SheetProtectionFlags, et as InspectReport, f as StreamSheetInfo, fn as FontStyle, fr as ERROR_CODES, ft as Cell, g as readTableStream, gn as ProtectionStyle, gr as XlvError, gt as Conformance, h as openWorkbookStream, hn as PatternType, hr as XlvDetails, ht as ColumnInfo, i as WriteTableStreamOptions, in as AlignmentStyle, ir as HashPasswordOptions, it as inspect, j as writeTable, jn as AnchorMarker, jt as WorkbookFormat, k as readTableAsync, kn as ChartType, kt as SplitPanes, l as ReadTableStreamOptions, ln as Color, lr as verifyPassword, lt as NoteBoxInput, m as WorkbookStream, mn as HorizontalAlignment, mr as XlvDetailValue, mt as CellValue, n as StreamRowInput, nn as IconSetType, nr as WorkbookProtection, nt as InspectTypeCounts, o as writeTableStream, on as BorderLineStyle, or as PasswordVerifier, ot as readWorkbook, p as TableStreamItem, pn as GradientFill, pr as WARNING_CODES, pt as CellType, q as WorkbookViewOptions, qn as PageSetup, qt as ConditionalFormatType, r as StreamSheetSpec, rn as TimePeriod, rr as WorkbookProtectionOptions, rt as InspectValue, s as writeWorkbookStream, sn as BorderStyle, sr as hashPassword, st as Note, t as StreamDefinedName, tn as DataValidationType, tr as SheetProtectionOptions, tt as InspectSheet, u as STREAM_LIMIT_DEFAULTS, un as FillStyle, ur as ShaAlgorithm, ut as NoteInput, v as ReadTableOptions, vn as UnderlineStyle, vr as XlvErrorInit, vt as Formula, w as TableValue, wn as ChartKind, wr as DEFAULT_READ_LIMITS, wt as SheetKind, x as TableIssue, xn as ChartAxis, xr as XlvWarning, xt as RichTextRun, y as TableColumn, yn as VerticalAlignRun, yr as XlvErrorJSON, yt as FormulaKind, z as ErrorValue, zn as imageFormat, zt as CfIcon } from "./stream-write-XnCyLdOy.js";
1
+ import { $ as InspectOptions, $n as SheetProtection, $t as DataValidationOperator, A as toJSON, An as SheetChart, At as Workbook, B as FormulaResult, Bn as Hyperlink, Bt as CfValue, C as TableRow, Cn as ChartInput, Cr as isXlvError, Ct as Sheet, D as fromJSON, Dn as ChartSeriesInput, Dr as DefinedNameOptions, Dt as SheetState, E as WriteTableOptions, En as ChartSeries, Er as DefinedName, Et as SheetProperties, F as CellInput, Fn as ImageOptions, Ft as TableDefinitionColumn, G as SheetPropertiesOptions, Gn as PageOrder, Gt as ConditionalFormatRule, H as NewSheet, Hn as HeaderFooter, Ht as CfValueType, I as ColumnOptions, In as ImagePositionInput, It as TableInput, J as WriteWorkbookOptions, Jn as PageSetupOptions, Jt as DataBarAxisPosition, K as SheetViewOptions, Kn as PageOrientation, Kt as ConditionalFormatRuleInput, L as CreateWorkbookOptions, Ln as SheetImage, Lt as TableStyleOptions, M as StandardSchemaV1, Mn as ImageAnchor, Mt as WorkbookView, N as AddSheetOptions, Nn as ImageAnchorInput, Nt as TableColumnInput, O as readTable, On as ChartText, Ot as SheetView, P as CalculationOptions, Pn as ImageFormat, Pt as TableDefinition, Q as InspectDefinedName, Qn as PrintTitles, Qt as DataValidationImeMode, R as ERROR_VALUES, Rn as WorkbookImage, Rt as TotalsRowFunction, S as TableResult, Sn as ChartAxisInput, Sr as XlvWarningCode, St as RowInfo, T as ToJSONOptions, Tn as ChartLegendPosition, Tr as ReadLimits, Tt as SheetLayout, U as NewWorkbook, Un as PageBreaks, Ut as ConditionalFormat, V as FreezeOptions, Vn as HyperlinkTarget, Vt as CfValueInput, W as RowOptions, Wn as PageMargins, Wt as ConditionalFormatOperator, X as writeWorkbook, Xn as PrintErrors, Xt as DataValidation, Y as createWorkbook, Yn as PrintCellComments, Yt as DataBarDirection, Z as InspectColumn, Zn as PrintOptions, Zt as DataValidationErrorStyle, _ as JsonTable, _n as ReadingOrder, _r as XlvErrorCode, _t as DateSystem, a as WriteWorkbookStreamOptions, an as BorderEdge, ar as PasswordHash, at as ReadOptions, b as TableColumnSpec, bn as VerticalAlignment, br as XlvLocation, bt as FrozenPanes, c as OpenWorkbookStreamOptions, cn as CellStyle, cr as legacyPasswordHash, ct as NoteBox, d as StreamRow, dn as FontScheme, dr as WorkbookProperties, dt as NoteMargins, en as DataValidationRule, er as SheetProtectionFlags, et as InspectReport, f as StreamSheetInfo, fn as FontStyle, fr as ERROR_CODES, ft as Cell, g as readTableStream, gn as ProtectionStyle, gr as XlvError, gt as Conformance, h as openWorkbookStream, hn as PatternType, hr as XlvDetails, ht as ColumnInfo, i as WriteTableStreamOptions, in as AlignmentStyle, ir as HashPasswordOptions, it as inspect, j as writeTable, jn as AnchorMarker, jt as WorkbookFormat, k as readTableAsync, kn as ChartType, kt as SplitPanes, l as ReadTableStreamOptions, ln as Color, lr as verifyPassword, lt as NoteBoxInput, m as WorkbookStream, mn as HorizontalAlignment, mr as XlvDetailValue, mt as CellValue, n as StreamRowInput, nn as IconSetType, nr as WorkbookProtection, nt as InspectTypeCounts, o as writeTableStream, on as BorderLineStyle, or as PasswordVerifier, ot as readWorkbook, p as TableStreamItem, pn as GradientFill, pr as WARNING_CODES, pt as CellType, q as WorkbookViewOptions, qn as PageSetup, qt as ConditionalFormatType, r as StreamSheetSpec, rn as TimePeriod, rr as WorkbookProtectionOptions, rt as InspectValue, s as writeWorkbookStream, sn as BorderStyle, sr as hashPassword, st as Note, t as StreamDefinedName, tn as DataValidationType, tr as SheetProtectionOptions, tt as InspectSheet, u as STREAM_LIMIT_DEFAULTS, un as FillStyle, ur as ShaAlgorithm, ut as NoteInput, v as ReadTableOptions, vn as UnderlineStyle, vr as XlvErrorInit, vt as Formula, w as TableValue, wn as ChartKind, wr as DEFAULT_READ_LIMITS, wt as SheetKind, x as TableIssue, xn as ChartAxis, xr as XlvWarning, xt as RichTextRun, y as TableColumn, yn as VerticalAlignRun, yr as XlvErrorJSON, yt as FormulaKind, z as ErrorValue, zn as imageFormat, zt as CfIcon } from "./stream-write-BbazTExn.js";
2
2
  /**
3
3
  * Options for {@link toCSV}.
4
4
  *
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Bn as isXlvError, Gt as hashPassword, Kt as legacyPasswordHash, Ln as ERROR_CODES, Lt as DEFAULT_READ_LIMITS, Qt as imageFormat, Rn as WARNING_CODES, _ as inspect, c as NewSheet, dn as shiftFormula, f as createWorkbook, l as NewWorkbook, m as writeWorkbook, qt as verifyPassword, s as ERROR_VALUES, x as readWorkbook, zn as XlvError } from "./options-Cxt-LJE4.js";
2
- import { a as appendRows, c as fromCSV, d as fromJSON, g as writeTable, h as toJSON, i as adjustRange, m as readTableAsync, n as insertRows, o as editCells, p as readTable, r as adjustFormula, s as diff, t as deleteRows, u as toCSV } from "./rows-DltZ-hiW.js";
3
- import { a as readTableStream, i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS, t as writeTableStream } from "./stream-write-D573ewpr.js";
1
+ import { Bn as isXlvError, Gt as hashPassword, Kt as legacyPasswordHash, Ln as ERROR_CODES, Lt as DEFAULT_READ_LIMITS, Qt as imageFormat, Rn as WARNING_CODES, _ as inspect, c as NewSheet, dn as shiftFormula, f as createWorkbook, l as NewWorkbook, m as writeWorkbook, qt as verifyPassword, s as ERROR_VALUES, x as readWorkbook, zn as XlvError } from "./options-BD873QNd.js";
2
+ import { a as appendRows, c as fromCSV, d as fromJSON, g as writeTable, h as toJSON, i as adjustRange, m as readTableAsync, n as insertRows, o as editCells, p as readTable, r as adjustFormula, s as diff, t as deleteRows, u as toCSV } from "./rows-BxGneQTL.js";
3
+ import { a as readTableStream, i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS, t as writeTableStream } from "./stream-write-BaUo8z8l.js";
4
4
  export { DEFAULT_READ_LIMITS, ERROR_CODES, ERROR_VALUES, NewSheet, NewWorkbook, STREAM_LIMIT_DEFAULTS, WARNING_CODES, XlvError, adjustFormula, adjustRange, appendRows, createWorkbook, deleteRows, diff, editCells, fromCSV, fromJSON, hashPassword, imageFormat, insertRows, inspect, isXlvError, legacyPasswordHash, openWorkbookStream, readTable, readTableAsync, readTableStream, readWorkbook, shiftFormula, toCSV, toJSON, verifyPassword, writeTable, writeTableStream, writeWorkbook, writeWorkbookStream };
@@ -1,4 +1,4 @@
1
- import { Bt as resolveLimits, zn as XlvError } from "./options-Cxt-LJE4.js";
1
+ import { Bt as resolveLimits, zn as XlvError } from "./options-BD873QNd.js";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  // src/util/node-files.ts
4
4
  /** The path as shown in messages. */
package/dist/node.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as InspectOptions, At as Workbook, J as WriteWorkbookOptions, U as NewWorkbook, a as WriteWorkbookStreamOptions, at as ReadOptions, c as OpenWorkbookStreamOptions, et as InspectReport, m as WorkbookStream } from "./stream-write-XnCyLdOy.js";
1
+ import { $ as InspectOptions, At as Workbook, J as WriteWorkbookOptions, U as NewWorkbook, a as WriteWorkbookStreamOptions, at as ReadOptions, c as OpenWorkbookStreamOptions, et as InspectReport, m as WorkbookStream } from "./stream-write-BbazTExn.js";
2
2
  /**
3
3
  * Reads an XLSX file from disk. Node.js only; in other runtimes pass the bytes to
4
4
  * `readWorkbook` from `xlvane`. The file size is checked against `limits.maxInputBytes`
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Bt as resolveLimits, _ as inspect, m as writeWorkbook, x as readWorkbook, zn as XlvError } from "./options-Cxt-LJE4.js";
2
- import { i as readInput, n as describePath, r as fileError, t as checkFile } from "./node-files-SVg17pky.js";
3
- import { i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS } from "./stream-write-D573ewpr.js";
1
+ import { Bt as resolveLimits, _ as inspect, m as writeWorkbook, x as readWorkbook, zn as XlvError } from "./options-BD873QNd.js";
2
+ import { i as readInput, n as describePath, r as fileError, t as checkFile } from "./node-files-BkN3L0JT.js";
3
+ import { i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS } from "./stream-write-BaUo8z8l.js";
4
4
  import { openAsBlob } from "node:fs";
5
5
  import { open, unlink, writeFile } from "node:fs/promises";
6
6
  // src/node.ts
@@ -5889,11 +5889,20 @@ function parseDxf(el, ns, colorOf, warn) {
5889
5889
  function parseDxfElement(el, ns, warn) {
5890
5890
  return parseDxf(el, ns, (c, what) => c === void 0 ? void 0 : parseColor(c, what, warn), warn);
5891
5891
  }
5892
- /** Parses CT_Color (W2-16): rgb as ARGB, or theme (with tint), indexed or auto. */
5892
+ /**
5893
+ * Parses CT_Color (W2-16): rgb as ARGB, or theme (with tint), indexed or auto. An rgb of exactly
5894
+ * 6 hex digits is not valid (ST_UnsignedIntHex has 4 bytes) but some writers produce it; it is
5895
+ * read as an opaque RGB colour, alpha FF, with a warning (W2-16a).
5896
+ */
5893
5897
  function parseColor(el, what, warn) {
5894
5898
  const rgb = el.attrs.get("rgb");
5895
5899
  if (rgb !== void 0) {
5896
5900
  if (/^[0-9A-Fa-f]{8}$/.test(rgb)) return rgb.toUpperCase();
5901
+ if (/^[0-9A-Fa-f]{6}$/.test(rgb)) {
5902
+ const argb = `FF${rgb.toUpperCase()}`;
5903
+ warn(`The ${what} colour ${quote(rgb)} is not an ARGB value; it was read as the opaque RGB colour "${argb}".`);
5904
+ return argb;
5905
+ }
5897
5906
  warn(`The ${what} colour ${quote(rgb)} is not an ARGB value; it is ignored.`);
5898
5907
  return;
5899
5908
  }
@@ -7798,15 +7807,20 @@ function reusableBuiltinNumFmt(code) {
7798
7807
  /**
7799
7808
  * Builds the styles part. With `identity`, cell format `i` is interned style `i` (no merging of
7800
7809
  * styles that give the same record), so a streaming writer can use style ids as `s` values
7801
- * before the part is built.
7810
+ * before the part is built. `defaultFont` is font 0, the workbook's default font (W2-12); its
7811
+ * name and size, or those of {@link DEFAULT_FONT}, complete the other fonts.
7802
7812
  */
7803
- function buildStyles(styles, identity = false, dxfs = []) {
7813
+ function buildStyles(styles, identity = false, dxfs = [], defaultFont = null) {
7804
7814
  const fonts = new Table();
7805
7815
  const fills = new Table();
7806
7816
  const borders = new Table();
7807
7817
  const xfs = new Table();
7808
7818
  const numFmts = /* @__PURE__ */ new Map();
7809
- fonts.add(fontXml({}));
7819
+ const fontDefaults = {
7820
+ name: defaultFont?.name ?? DEFAULT_FONT.name,
7821
+ size: defaultFont?.size ?? DEFAULT_FONT.size
7822
+ };
7823
+ fonts.add(fontXml(defaultFont ?? {}, fontDefaults));
7810
7824
  fills.add("<fill><patternFill patternType=\"none\"/></fill>");
7811
7825
  fills.add("<fill><patternFill patternType=\"gray125\"/></fill>");
7812
7826
  borders.add("<border><left/><right/><top/><bottom/><diagonal/></border>");
@@ -7823,7 +7837,7 @@ function buildStyles(styles, identity = false, dxfs = []) {
7823
7837
  return id;
7824
7838
  };
7825
7839
  const xfOf = styles.map((style) => {
7826
- const fontId = style.font ? fonts.add(fontXml(style.font)) : 0;
7840
+ const fontId = style.font ? fonts.add(fontXml(style.font, fontDefaults)) : 0;
7827
7841
  let fillId = 0;
7828
7842
  if (style.fill) fillId = fills.add(fillXml(style.fill));
7829
7843
  let borderId = 0;
@@ -8037,9 +8051,15 @@ function sheetHead(sheet, selected, dimension, xfOf) {
8037
8051
  }
8038
8052
  return `${xml}<sheetData>`;
8039
8053
  }
8040
- /** The start tag of a row (without `>` for rows that have cells: callers add `>` or `/>`). */
8041
- function rowStart(r, info) {
8054
+ /**
8055
+ * The start tag of a row (without `>` for rows that have cells: callers add `>` or `/>`). `xfOf`
8056
+ * maps interned style ids to cell format indexes (identity when omitted).
8057
+ */
8058
+ function rowStart(r, info, xfOf) {
8042
8059
  let xml = `<row${attr("r", r)}`;
8060
+ const style = info?.style ?? 0;
8061
+ const xf = style === 0 ? 0 : xfOf?.[style] ?? style;
8062
+ if (xf !== 0) xml += `${attr("s", xf)} customFormat="1"`;
8043
8063
  if (info?.height != null) xml += `${attr("ht", info.height)} customHeight="1"`;
8044
8064
  if (info?.hidden) xml += " hidden=\"1\"";
8045
8065
  if (info?.outlineLevel) xml += attr("outlineLevel", info.outlineLevel);
@@ -8418,7 +8438,7 @@ function sheetXml(sheet, selected, xfOf, sst, rels, after = "", sheetNumber = 1)
8418
8438
  const groups = new FormulaGroups(sheet.name);
8419
8439
  for (const r of sortedRows) {
8420
8440
  const cells = sheet.cells.get(r);
8421
- const start = rowStart(r, sheet.rows.get(r));
8441
+ const start = rowStart(r, sheet.rows.get(r), xfOf);
8422
8442
  if (!cells || cells.size === 0) {
8423
8443
  xml += `${start}/>`;
8424
8444
  continue;
@@ -8439,7 +8459,7 @@ function sheetXml(sheet, selected, xfOf, sst, rels, after = "", sheetNumber = 1)
8439
8459
  }
8440
8460
  /** Builds all parts and returns the zipped package. */
8441
8461
  function writePackage(input) {
8442
- const { xml: stylesXml, xfOf } = buildStyles(input.styles, false, input.dxfs);
8462
+ const { xml: stylesXml, xfOf } = buildStyles(input.styles, false, input.dxfs, input.defaultFont);
8443
8463
  const shared = input.sharedStrings !== false;
8444
8464
  const sst = shared ? new SharedStrings() : null;
8445
8465
  const active = input.sheets.findIndex((s) => s.state === "visible");
@@ -10502,10 +10522,23 @@ function createWorksheetParser(ctx, onRow) {
10502
10522
  if (ht !== void 0 && (height === null || height < 0)) ctx.warnings.add("XLV_W_INVALID_VALUE", `The row height "${ht.slice(0, 20)}" is not a valid number; it is ignored.`, loc());
10503
10523
  const validHeight = height !== null && height >= 0 ? height : null;
10504
10524
  const outline = parseOutline(attrs);
10505
- if (validHeight !== null || hidden || outline.outlineLevel !== void 0 || outline.collapsed !== void 0) rowInfo.set(currentRow, {
10525
+ const customFormat = attrs.get("customFormat");
10526
+ let style;
10527
+ if (customFormat !== void 0 && parseXsdBoolean(customFormat) === true) {
10528
+ const s = attrs.get("s") ?? "0";
10529
+ const index = parseUint(s, 10);
10530
+ if (index === null) ctx.warnings.add("XLV_W_INVALID_VALUE", `The row style index "${s.slice(0, 20)}" is not a number; it is ignored.`, loc());
10531
+ else style = ctx.styles.cellStyle(index, numberFormat(index).code);
10532
+ }
10533
+ if (validHeight !== null || hidden || outline.outlineLevel !== void 0 || outline.collapsed !== void 0 || style !== void 0) rowInfo.set(currentRow, style === void 0 ? {
10506
10534
  height: validHeight,
10507
10535
  hidden,
10508
10536
  ...outline
10537
+ } : {
10538
+ height: validHeight,
10539
+ hidden,
10540
+ ...outline,
10541
+ style
10509
10542
  });
10510
10543
  } else if (depth === 4 && local === "c" && parent === "row" && path[1] === "sheetData") {
10511
10544
  if (skipRow) return;
@@ -11528,9 +11561,10 @@ var ReadWorkbook = class {
11528
11561
  protection;
11529
11562
  views;
11530
11563
  calculation;
11564
+ defaultFont;
11531
11565
  /** Image parts used by pictures and backgrounds, each once (TD-31). */
11532
11566
  images = [];
11533
- constructor(sheets, dateSystem, conformance, format, hasMacros, externalLinkCount, warnings, properties, definedNames, protection = null, views = [], calculation = null) {
11567
+ constructor(sheets, dateSystem, conformance, format, hasMacros, externalLinkCount, warnings, properties, definedNames, protection = null, views = [], calculation = null, defaultFont = null) {
11534
11568
  this.sheets = sheets;
11535
11569
  this.dateSystem = dateSystem;
11536
11570
  this.conformance = conformance;
@@ -11543,6 +11577,7 @@ var ReadWorkbook = class {
11543
11577
  this.protection = protection;
11544
11578
  this.views = views;
11545
11579
  this.calculation = calculation;
11580
+ this.defaultFont = defaultFont;
11546
11581
  }
11547
11582
  getSheet(name) {
11548
11583
  const sheet = this.sheets.find((s) => s.name === name);
@@ -11648,7 +11683,7 @@ function readWorkbook(data, options) {
11648
11683
  sheet.charts = drawing.charts;
11649
11684
  sheet.backgroundImage = drawing.background;
11650
11685
  });
11651
- const workbook = new ReadWorkbook(sheets, book.dateSystem, book.conformance, book.format, book.hasMacros, book.externalLinkCount, warnings.list(), book.properties, book.definedNames, book.protection, book.views, book.calculation);
11686
+ const workbook = new ReadWorkbook(sheets, book.dateSystem, book.conformance, book.format, book.hasMacros, book.externalLinkCount, warnings.list(), book.properties, book.definedNames, book.protection, book.views, book.calculation, book.styles.defaultFont() ?? null);
11652
11687
  workbook.images = [...media.values()].filter((m) => m !== null);
11653
11688
  return workbook;
11654
11689
  }
@@ -12967,13 +13002,13 @@ var NewSheet = class {
12967
13002
  });
12968
13003
  }
12969
13004
  /**
12970
- * Sets the height or visibility of a row (1-based).
13005
+ * Sets the height, visibility, outline or default style of a row (1-based).
12971
13006
  *
12972
13007
  * @example
12973
13008
  * ```ts
12974
13009
  * import { createWorkbook } from 'xlvane';
12975
13010
  *
12976
- * createWorkbook().addSheet('Data').row(1, { height: 30 }).row(5, { hidden: true });
13011
+ * createWorkbook().addSheet('Data').row(1, { height: 30, style: { font: { bold: true } } }).row(5, { hidden: true });
12977
13012
  * ```
12978
13013
  */
12979
13014
  row(row, options) {
@@ -12982,12 +13017,14 @@ var NewSheet = class {
12982
13017
  const o = readOptions$1(options, [
12983
13018
  "height",
12984
13019
  "hidden",
13020
+ "style",
12985
13021
  "outlineLevel",
12986
13022
  "collapsed"
12987
13023
  ], "row options");
12988
13024
  const current = this.rowMap.get(row) ?? {
12989
13025
  height: null,
12990
13026
  hidden: false,
13027
+ style: 0,
12991
13028
  outlineLevel: 0,
12992
13029
  collapsed: false
12993
13030
  };
@@ -13001,6 +13038,11 @@ var NewSheet = class {
13001
13038
  if (typeof hidden !== "boolean") throw invalidOption("Row option \"hidden\" must be true or false.", "Use { hidden: true }.");
13002
13039
  current.hidden = hidden;
13003
13040
  }
13041
+ const style = o.get("style");
13042
+ if (style !== void 0) current.style = this.book.intern(located({
13043
+ sheet: this.name,
13044
+ cell: `${String(row)}:${String(row)}`
13045
+ }, () => normalizeStyle(style)));
13004
13046
  readOutline(o, current, "Row");
13005
13047
  this.rowMap.set(row, current);
13006
13048
  return this;
@@ -14041,10 +14083,13 @@ var NewWorkbook = class {
14041
14083
  bookViews = [];
14042
14084
  /** @internal `calcPr`, set by {@link NewWorkbook.calculation}. */
14043
14085
  calcProps = null;
14086
+ /** @internal Font 0 given to {@link createWorkbook}, or `null` for the default (W2-12). */
14087
+ fontDefault;
14044
14088
  /** @internal Use {@link createWorkbook}. */
14045
- constructor(dateSystem, properties = {}) {
14089
+ constructor(dateSystem, properties = {}, defaultFont = null) {
14046
14090
  this.dateSystem = dateSystem;
14047
14091
  this.properties = properties;
14092
+ this.fontDefault = defaultFont;
14048
14093
  this.styleIds.set(stableKey({}), 0);
14049
14094
  }
14050
14095
  /**
@@ -14374,10 +14419,16 @@ var NewWorkbook = class {
14374
14419
  * ```
14375
14420
  */
14376
14421
  function createWorkbook(options) {
14377
- const o = readOptions$1(options, ["dateSystem", "properties"], "workbook options");
14422
+ const o = readOptions$1(options, [
14423
+ "dateSystem",
14424
+ "properties",
14425
+ "defaultFont"
14426
+ ], "workbook options");
14378
14427
  const dateSystem = o.get("dateSystem") ?? "1900";
14379
14428
  if (dateSystem !== "1900" && dateSystem !== "1904") throw invalidOption(`Date system ${JSON.stringify(String(dateSystem).slice(0, 20))} is not valid.`, "Use \"1900\" (the default) or \"1904\".");
14380
- return new NewWorkbook(dateSystem, normalizeProperties(o.get("properties")));
14429
+ const font = o.get("defaultFont");
14430
+ const defaultFont = font === void 0 ? null : normalizeStyle({ font }).font ?? null;
14431
+ return new NewWorkbook(dateSystem, normalizeProperties(o.get("properties")), defaultFont);
14381
14432
  }
14382
14433
  /**
14383
14434
  * Checks that `activeTab` and `firstSheet` of every workbook window name an existing sheet: both
@@ -14439,7 +14490,8 @@ function writeWorkbook(workbook, options = {}) {
14439
14490
  definedNames: [...workbook.definedNames, ...sheets.flatMap((s) => printNames(s.name, s.print))],
14440
14491
  dxfs: workbook.dxfStyles(),
14441
14492
  workbookProtection: workbook.workbookProtection,
14442
- images: workbook.imageList()
14493
+ images: workbook.imageList(),
14494
+ defaultFont: workbook.fontDefault
14443
14495
  });
14444
14496
  }
14445
14497
  // src/util/options.ts
@@ -1,4 +1,4 @@
1
- import { $ as fillXml, An as decodeXml, At as parseStyles, Bt as resolveLimits, C as toBytes, Ct as relationshipsPartName, Dn as childrenNamed, En as childNamed, Fn as escapeAttr, Ft as stableKey, G as DEFAULT_FONT, H as X14_DV_URI, In as escapeText, It as isDateFormat, J as borderXml, Lt as DEFAULT_READ_LIMITS, Mn as parseXml, Mt as checkStyleCount, Nt as mergeStyles, On as parseXmlTree, P as openZip, Pn as encodeXstring, Pt as normalizeStyle, R as WarningCollector, Rt as checkKeyText, S as styledCellCount, St as openPackage, V as X14_CF_URI, Vt as suggest, _n as formatRange, an as relationshipTypes, at as reusableBuiltinNumFmt, bn as partKey, bt as parseIsoDateTime, dn as shiftFormula, et as fontXml, f as createWorkbook, fn as tokenize, ft as stringItemXml, g as detectHeader, gn as formatCellRef, h as RangeIndex, hn as columnLetters, i as readOptions, in as classifyRelationship, jn as normalizeLineEnds, jt as READING_ORDERS, kt as EMPTY_STYLES, m as writeWorkbook, mn as MAX_ROWS, mt as textElement, n as oneOfOption, nn as REL_NS, pn as MAX_COLUMNS, q as attr, r as openInput, rn as SML_NS, t as booleanOption, tt as formatNumber, v as isoDate, vn as parseCellRef, vt as writeZip, x as readWorkbook, xn as zipNameToPartName, xt as parseSharedStrings, yn as parseRange, zn as XlvError, zt as checkOutputPositions } from "./options-Cxt-LJE4.js";
1
+ import { $ as fillXml, An as decodeXml, At as parseStyles, Bt as resolveLimits, C as toBytes, Ct as relationshipsPartName, Dn as childrenNamed, En as childNamed, Fn as escapeAttr, Ft as stableKey, G as DEFAULT_FONT, H as X14_DV_URI, In as escapeText, It as isDateFormat, J as borderXml, Lt as DEFAULT_READ_LIMITS, Mn as parseXml, Mt as checkStyleCount, Nt as mergeStyles, On as parseXmlTree, P as openZip, Pn as encodeXstring, Pt as normalizeStyle, R as WarningCollector, Rt as checkKeyText, S as styledCellCount, St as openPackage, V as X14_CF_URI, Vt as suggest, _n as formatRange, an as relationshipTypes, at as reusableBuiltinNumFmt, bn as partKey, bt as parseIsoDateTime, dn as shiftFormula, et as fontXml, f as createWorkbook, fn as tokenize, ft as stringItemXml, g as detectHeader, gn as formatCellRef, h as RangeIndex, hn as columnLetters, i as readOptions, in as classifyRelationship, jn as normalizeLineEnds, jt as READING_ORDERS, kt as EMPTY_STYLES, m as writeWorkbook, mn as MAX_ROWS, mt as textElement, n as oneOfOption, nn as REL_NS, pn as MAX_COLUMNS, q as attr, r as openInput, rn as SML_NS, t as booleanOption, tt as formatNumber, v as isoDate, vn as parseCellRef, vt as writeZip, x as readWorkbook, xn as zipNameToPartName, xt as parseSharedStrings, yn as parseRange, zn as XlvError, zt as checkOutputPositions } from "./options-BD873QNd.js";
2
2
  // src/table.ts
3
3
  const READ_OPTIONS = [
4
4
  "sheet",
@@ -1,4 +1,4 @@
1
- import { $t as metricsOf, A as checkSource$1, B as createWorksheetParser, Bt as resolveLimits, Cn as CORE_PROPERTIES_PART, Ct as relationshipsPartName, D as HEAD_BYTES, Dt as checkUniqueName, E as CORRUPT_HINT, Et as checkDefinedName, F as readCentralDirectory, Ht as printNames, I as unsupported, Jt as DRAWING_CONTENT_TYPE, K as SheetRels, L as validateLocalHeaders, Lt as DEFAULT_READ_LIMITS, M as limitExceeded, N as locateCentralDirectory, Nn as clip, O as NotLoaded, Ot as nameScopeKey, Pt as normalizeStyle, Q as createSharedStrings, R as WarningCollector, Rt as checkKeyText, Sn as APP_PROPERTIES_PART, St as openPackage, T as Bytes, Tn as corePropertiesXml, Tt as normalizeTable, Ut as printXml, Wt as sheetProtectionXml, X as cellXml, Xt as MediaParts, Y as buildStyles, Yt as IMAGE_CONTENT_TYPES, Z as contentTypesXml, Zt as drawingParts, _t as FormulaGroups, a as DATE_FORMAT, b as readNotes, bn as partKey, cn as chartPartXml, ct as sheetHead, d as convertCellInput, dt as sheetTail, en as resolveAnchor, f as createWorkbook, g as detectHeader, gn as formatCellRef, gt as workbookXml, hn as columnLetters, ht as workbookRelsXml, i as readOptions, in as classifyRelationship, it as noteParts, j as corrupt, k as TAIL_BYTES, kn as createXmlByteStreamParser, ln as chartSheetNames, lt as sheetRelsPartName, mn as MAX_ROWS, n as oneOfOption, nt as hyperlinksXml, o as DATE_TIME_FORMAT, on as CHART_CONTENT_TYPE, ot as rootRelsXml, p as sortedNotes, pn as MAX_COLUMNS, pt as tableParts, rt as nameTitles, sn as chartPartName, st as rowStart, tn as resolveImage, u as checkBookViews, un as resolveChartSheets, ut as sheetRulesXml, v as isoDate, w as workbookPartsToLoad, wn as appPropertiesXml, wt as TABLE_CONTENT_TYPE, xn as zipNameToPartName, y as analyzeWorkbook, yt as crc32, z as sheetNotFound, zn as XlvError, zt as checkOutputPositions } from "./options-Cxt-LJE4.js";
1
+ import { $t as metricsOf, A as checkSource$1, B as createWorksheetParser, Bt as resolveLimits, Cn as CORE_PROPERTIES_PART, Ct as relationshipsPartName, D as HEAD_BYTES, Dt as checkUniqueName, E as CORRUPT_HINT, Et as checkDefinedName, F as readCentralDirectory, Ht as printNames, I as unsupported, Jt as DRAWING_CONTENT_TYPE, K as SheetRels, L as validateLocalHeaders, Lt as DEFAULT_READ_LIMITS, M as limitExceeded, N as locateCentralDirectory, Nn as clip, O as NotLoaded, Ot as nameScopeKey, Pt as normalizeStyle, Q as createSharedStrings, R as WarningCollector, Rt as checkKeyText, Sn as APP_PROPERTIES_PART, St as openPackage, T as Bytes, Tn as corePropertiesXml, Tt as normalizeTable, Ut as printXml, Wt as sheetProtectionXml, X as cellXml, Xt as MediaParts, Y as buildStyles, Yt as IMAGE_CONTENT_TYPES, Z as contentTypesXml, Zt as drawingParts, _t as FormulaGroups, a as DATE_FORMAT, b as readNotes, bn as partKey, cn as chartPartXml, ct as sheetHead, d as convertCellInput, dt as sheetTail, en as resolveAnchor, f as createWorkbook, g as detectHeader, gn as formatCellRef, gt as workbookXml, hn as columnLetters, ht as workbookRelsXml, i as readOptions, in as classifyRelationship, it as noteParts, j as corrupt, k as TAIL_BYTES, kn as createXmlByteStreamParser, ln as chartSheetNames, lt as sheetRelsPartName, mn as MAX_ROWS, n as oneOfOption, nt as hyperlinksXml, o as DATE_TIME_FORMAT, on as CHART_CONTENT_TYPE, ot as rootRelsXml, p as sortedNotes, pn as MAX_COLUMNS, pt as tableParts, rt as nameTitles, sn as chartPartName, st as rowStart, tn as resolveImage, u as checkBookViews, un as resolveChartSheets, ut as sheetRulesXml, v as isoDate, w as workbookPartsToLoad, wn as appPropertiesXml, wt as TABLE_CONTENT_TYPE, xn as zipNameToPartName, y as analyzeWorkbook, yt as crc32, z as sheetNotFound, zn as XlvError, zt as checkOutputPositions } from "./options-BD873QNd.js";
2
2
  import { Deflate, Inflate } from "fflate";
3
3
  // src/zip/stream.ts
4
4
  /**
@@ -586,13 +586,19 @@ async function openWorkbookStream(source, options) {
586
586
  formulaText: limits.maxSharedStringChars
587
587
  },
588
588
  formatCache
589
- }, (number, cells, rowInfo, styledCells) => ready.push({
590
- number,
591
- cells: withNotes(cells),
592
- styledCells,
593
- height: rowInfo?.height ?? null,
594
- hidden: rowInfo?.hidden ?? false
595
- }));
589
+ }, (number, cells, rowInfo, styledCells) => {
590
+ const row = {
591
+ number,
592
+ cells: withNotes(cells),
593
+ styledCells,
594
+ height: rowInfo?.height ?? null,
595
+ hidden: rowInfo?.hidden ?? false
596
+ };
597
+ ready.push(rowInfo?.style === void 0 ? row : {
598
+ ...row,
599
+ style: rowInfo.style
600
+ });
601
+ });
596
602
  const parser = createXmlByteStreamParser(handler, {
597
603
  part,
598
604
  limits
@@ -868,12 +874,14 @@ function writeWorkbookStream(options) {
868
874
  "images",
869
875
  "views",
870
876
  "calculation",
871
- "compressionLevel"
877
+ "compressionLevel",
878
+ "defaultFont"
872
879
  ], "writeWorkbookStream options");
873
880
  const dateSystem = o.get("dateSystem");
874
881
  const book = createWorkbook({
875
882
  ...dateSystem === void 0 ? {} : { dateSystem },
876
- ...o.has("properties") ? { properties: o.get("properties") } : {}
883
+ ...o.has("properties") ? { properties: o.get("properties") } : {},
884
+ ...o.has("defaultFont") ? { defaultFont: o.get("defaultFont") } : {}
877
885
  });
878
886
  if (o.has("protection")) book.protect(o.get("protection"));
879
887
  if (o.has("views")) book.views(o.get("views"));
@@ -1128,6 +1136,7 @@ function writeWorkbookStream(options) {
1128
1136
  "values",
1129
1137
  "style",
1130
1138
  "styles",
1139
+ "rowStyle",
1131
1140
  "height",
1132
1141
  "hidden",
1133
1142
  "outlineLevel",
@@ -1151,6 +1160,7 @@ function writeWorkbookStream(options) {
1151
1160
  "outlineLevel",
1152
1161
  "collapsed"
1153
1162
  ]) if (ro.get(key) !== void 0) format[key] = ro.get(key);
1163
+ if (ro.get("rowStyle") !== void 0) format.style = ro.get("rowStyle");
1154
1164
  if (Object.keys(format).length > 0) {
1155
1165
  p.sheet.row(r, format);
1156
1166
  info = p.sheet.rowMap.get(r);
@@ -1308,7 +1318,7 @@ function writeWorkbookStream(options) {
1308
1318
  };
1309
1319
  yield {
1310
1320
  name: "xl/styles.xml",
1311
- data: encoder.encode(buildStyles(book.styles(), true, book.dxfStyles()).xml)
1321
+ data: encoder.encode(buildStyles(book.styles(), true, book.dxfStyles(), book.fontDefault).xml)
1312
1322
  };
1313
1323
  if (core !== null) yield {
1314
1324
  name: CORE_PROPERTIES_PART,
@@ -2605,7 +2605,7 @@ interface ColumnInfo {
2605
2605
  readonly collapsed?: boolean;
2606
2606
  }
2607
2607
  /**
2608
- * Height and visibility of a row.
2608
+ * Height, visibility, outline and default style of a row.
2609
2609
  *
2610
2610
  * @example
2611
2611
  * ```ts
@@ -2613,13 +2613,19 @@ interface ColumnInfo {
2613
2613
  *
2614
2614
  * declare const bytes: Uint8Array;
2615
2615
  * const first: RowInfo | undefined = readWorkbook(bytes).sheets[0]?.rowInfo(1);
2616
- * console.log(first?.height, first?.hidden);
2616
+ * console.log(first?.height, first?.hidden, first?.style?.font?.bold);
2617
2617
  * ```
2618
2618
  */
2619
2619
  interface RowInfo {
2620
2620
  /** Height in points, or `null` if the file does not set one. */
2621
2621
  readonly height: number | null;
2622
2622
  readonly hidden: boolean;
2623
+ /**
2624
+ * The row's default style (`row@s`), resolved like `Cell.style`; present only when the file
2625
+ * marks the row as formatted (`customFormat="1"`, without which `s` does not apply, ECMA-376
2626
+ * Part 1 §18.3.1.73). It is not merged into `Cell.style` of the row's cells.
2627
+ */
2628
+ readonly style?: CellStyle;
2623
2629
  /** Outline level 1 to 7; absent for 0. */
2624
2630
  readonly outlineLevel?: number;
2625
2631
  /** Present and `true` when the row's outline group is collapsed. */
@@ -2946,7 +2952,7 @@ interface Sheet {
2946
2952
  *
2947
2953
  * declare const bytes: Uint8Array;
2948
2954
  * const workbook: Workbook = readWorkbook(bytes);
2949
- * console.log(workbook.sheets.map((sheet) => sheet.name), workbook.hasMacros);
2955
+ * console.log(workbook.sheets.map((sheet) => sheet.name), workbook.hasMacros, workbook.defaultFont?.name);
2950
2956
  * ```
2951
2957
  */
2952
2958
  interface Workbook {
@@ -2974,6 +2980,11 @@ interface Workbook {
2974
2980
  } | null;
2975
2981
  /** The images used by pictures and sheet backgrounds, each once (see `WorkbookImage`). */
2976
2982
  readonly images: readonly WorkbookImage[];
2983
+ /**
2984
+ * The workbook's default font: font 0 of the styles part (W2-12), the font of cells whose
2985
+ * `style` has no `font`. `null` when the file has no styles part or no fonts.
2986
+ */
2987
+ readonly defaultFont: FontStyle | null;
2977
2988
  /** Returns the sheet with this name; throws `XLV_E_SHEET_NOT_FOUND` if there is none. */
2978
2989
  getSheet(name: string): Sheet;
2979
2990
  }
@@ -3432,10 +3443,12 @@ interface WriteSheetData {
3432
3443
  readonly chart: NormalizedChart;
3433
3444
  })[];
3434
3445
  }
3435
- /** Height, visibility and outline of a row. */
3446
+ /** Height, visibility, outline and default style of a row. */
3436
3447
  interface RowFormat {
3437
3448
  readonly height: number | null;
3438
3449
  readonly hidden: boolean;
3450
+ /** Interned style id of the row's default style (0 = none). */
3451
+ readonly style?: number;
3439
3452
  readonly outlineLevel?: number;
3440
3453
  readonly collapsed?: boolean;
3441
3454
  }
@@ -3516,6 +3529,24 @@ interface CreateWorkbookOptions {
3516
3529
  readonly dateSystem?: DateSystem;
3517
3530
  /** Document properties (title, creator, dates …), written to `docProps/core.xml` when given. */
3518
3531
  readonly properties?: WorkbookProperties;
3532
+ /**
3533
+ * The workbook's default font: font 0 of the styles part, the font of cells without a font
3534
+ * of their own (W2-12). A missing name or size is Calibri 11, the default without this
3535
+ * option. The font's name and size also complete the fonts of styles that give no name or
3536
+ * size; its other properties (bold, colour, …) apply to font 0 only. Column widths are in
3537
+ * characters of the Normal style's font (ECMA-376 Part 1 §18.3.1.13), which is this font in
3538
+ * the files xlvane writes; the written default row height stays 15 points unless set with
3539
+ * `NewSheet.properties`. Checked like `CellStyle.font`.
3540
+ *
3541
+ * @example
3542
+ * ```ts
3543
+ * import { createWorkbook } from 'xlvane';
3544
+ *
3545
+ * const workbook = createWorkbook({ defaultFont: { name: 'Arial', size: 10 } });
3546
+ * workbook.addSheet('Data').set('A1', 'Arial 10').set('A2', 'Arial 10, bold', { font: { bold: true } });
3547
+ * ```
3548
+ */
3549
+ readonly defaultFont?: FontStyle;
3519
3550
  }
3520
3551
  /**
3521
3552
  * Options for {@link NewWorkbook.addSheet}.
@@ -3579,13 +3610,22 @@ interface ColumnOptions {
3579
3610
  * import { createWorkbook, type RowOptions } from 'xlvane';
3580
3611
  *
3581
3612
  * const tall: RowOptions = { height: 30 };
3582
- * createWorkbook().addSheet('Data').row(1, tall);
3613
+ * const header: RowOptions = { style: { font: { bold: true } } };
3614
+ * createWorkbook().addSheet('Data').row(1, tall).row(2, header);
3583
3615
  * ```
3584
3616
  */
3585
3617
  interface RowOptions {
3586
3618
  /** Height in points, 0 to 409.5. */
3587
3619
  readonly height?: number;
3588
3620
  readonly hidden?: boolean;
3621
+ /**
3622
+ * Default style of the row, written as `row@s` with `customFormat="1"` (the row's cell format;
3623
+ * `s` applies only when `customFormat` is 1, ECMA-376 Part 1 §18.3.1.73). xlvane does not apply
3624
+ * it to the row's cells: style cells with `set` or `style`. LibreOffice 24.2 gives the cells of
3625
+ * the row that have no style of their own the row's style when it opens the file. A row with
3626
+ * only a style is written. `{}` removes it.
3627
+ */
3628
+ readonly style?: CellStyle;
3589
3629
  /** Outline (grouping) level, 0 to 7 (`row@outlineLevel`; [MS-OI29500] §2.1.644 b). Default 0. */
3590
3630
  readonly outlineLevel?: number;
3591
3631
  /** Whether the outline group ending at this row is collapsed (`row@collapsed`). Default false. */
@@ -3826,6 +3866,7 @@ declare class NewSheet {
3826
3866
  readonly rowMap: Map<number, {
3827
3867
  height: number | null;
3828
3868
  hidden: boolean;
3869
+ style: number;
3829
3870
  outlineLevel: number;
3830
3871
  collapsed: boolean;
3831
3872
  }>;
@@ -3936,13 +3977,13 @@ declare class NewSheet {
3936
3977
  */
3937
3978
  column(col: string | number, options: ColumnOptions): this;
3938
3979
  /**
3939
- * Sets the height or visibility of a row (1-based).
3980
+ * Sets the height, visibility, outline or default style of a row (1-based).
3940
3981
  *
3941
3982
  * @example
3942
3983
  * ```ts
3943
3984
  * import { createWorkbook } from 'xlvane';
3944
3985
  *
3945
- * createWorkbook().addSheet('Data').row(1, { height: 30 }).row(5, { hidden: true });
3986
+ * createWorkbook().addSheet('Data').row(1, { height: 30, style: { font: { bold: true } } }).row(5, { hidden: true });
3946
3987
  * ```
3947
3988
  */
3948
3989
  row(row: number, options: RowOptions): this;
@@ -4309,8 +4350,10 @@ declare class NewWorkbook {
4309
4350
  calcProps: {
4310
4351
  fullCalcOnLoad: boolean;
4311
4352
  } | null;
4353
+ /** @internal Font 0 given to {@link createWorkbook}, or `null` for the default (W2-12). */
4354
+ readonly fontDefault: NormalizedStyle["font"] | null;
4312
4355
  /** @internal Use {@link createWorkbook}. */
4313
- constructor(dateSystem: DateSystem, properties?: WorkbookProperties);
4356
+ constructor(dateSystem: DateSystem, properties?: WorkbookProperties, defaultFont?: NormalizedStyle["font"] | null);
4314
4357
  /**
4315
4358
  * Protects the workbook (`workbookProtection`, SR-22), by default its structure: sheets cannot
4316
4359
  * be added, deleted, moved or renamed in a spreadsheet application without the password.
@@ -4899,6 +4942,13 @@ interface StreamRow {
4899
4942
  /** Row height in points, or `null` if the file sets none. */
4900
4943
  readonly height: number | null;
4901
4944
  readonly hidden: boolean;
4945
+ /**
4946
+ * The row's default style, as `RowInfo.style`: present only when the file marks the row as
4947
+ * formatted (`row@s` with `customFormat="1"`). Rows without cells are not returned, so the
4948
+ * style of a row that has only a style is not either; `readWorkbook` gives it with
4949
+ * `Sheet.rowInfo`.
4950
+ */
4951
+ readonly style?: CellStyle;
4902
4952
  }
4903
4953
  /**
4904
4954
  * A sheet listed by {@link WorkbookStream}.
@@ -5087,6 +5137,7 @@ declare function readTableStream(source: Uint8Array | ArrayBuffer | Blob, option
5087
5137
  *
5088
5138
  * const plain: StreamRowInput = ['Alice', 42, new Date(Date.UTC(2024, 0, 31))];
5089
5139
  * const header: StreamRowInput = { values: ['Name', 'Total', 'Date'], style: { font: { bold: true } }, height: 20 };
5140
+ * const shaded: StreamRowInput = { values: ['Bob', 7], rowStyle: { fill: { color: 'EEEEEE' } } };
5090
5141
  * const linked: StreamRowInput = { values: ['Docs', 7], hyperlinks: [{ url: 'https://example.com' }], notes: [undefined, 'Estimate'] };
5091
5142
  * ```
5092
5143
  */
@@ -5096,6 +5147,12 @@ type StreamRowInput = readonly CellInput[] | {
5096
5147
  readonly style?: CellStyle;
5097
5148
  /** Style per cell, by position. */
5098
5149
  readonly styles?: readonly (CellStyle | undefined)[];
5150
+ /**
5151
+ * Default style of the row itself (`row@s` with `customFormat="1"`), as `RowOptions.style`
5152
+ * of `NewSheet.row`. It is not applied to the row's cells; use `style` or `styles` for them.
5153
+ * A row with only a row style and no values is written.
5154
+ */
5155
+ readonly rowStyle?: CellStyle;
5099
5156
  /** Row height in points. */
5100
5157
  readonly height?: number;
5101
5158
  readonly hidden?: boolean;
@@ -5234,6 +5291,8 @@ interface WriteWorkbookStreamOptions {
5234
5291
  readonly protection?: WorkbookProtectionOptions;
5235
5292
  /** Image files (PNG, JPEG or GIF bytes) used by the sheets' `images` and `background`, by position. */
5236
5293
  readonly images?: readonly Uint8Array[];
5294
+ /** The workbook's default font, as `CreateWorkbookOptions.defaultFont`. Default Calibri 11. */
5295
+ readonly defaultFont?: FontStyle;
5237
5296
  }
5238
5297
  /**
5239
5298
  * A defined name for {@link WriteWorkbookStreamOptions.definedNames}.
package/docs/errors.md CHANGED
@@ -112,7 +112,7 @@ From 1.3.0 (D-K04):
112
112
  - `maxCells`: `insertRows` with `style: 'above'` or `'below'` (and `xlvane edit --insert-rows` with a style) counts the formatted cells copied into the new rows (the formatted cells of the copied row times the number of rows) plus the cells of the workbook against `maxCells`, before the rows are built. Insert fewer rows, use `style: 'none'`, or raise `maxCells`.
113
113
  - `sharedFormulaText`: `@xlvane/compat` `cell.formula` of a `{ sharedFormula }` dependent moves the master's formula once and keeps the result; the text moved per workbook (master text read plus moved text built) is limited to 512 Mi characters, the default `maxSharedStringChars` with which `readWorkbook` expands shared formulas, and for masters longer than 8192 characters (the longest formula Office writes, [MS-OI29500] §2.1.1085 a) to 32 Mi characters; also in `stream.xlsx.WorkbookReader` (per read). `location` names the dependent cell; its cached `cell.result` stays readable.
114
114
 
115
- From 1.3.0 (D-K03): `@xlvane/compat` `xlsx.load` limits merged cells to `max(1 048 576, cells in the file)` (`details.limit` `mergedCells`; each merged cell is written with the master's style, so a few bytes of `mergeCell` could ask for millions of styled cells), and `csv.read` stops at more than 1 048 576 rows (`details.limit` `maxRows`) 16 384 fields in a row (`details.limit` `maxColumns`, with `location.line`) or 5 000 000 fields in all (`details.limit` `maxCells`) while it parses.
115
+ From 1.3.0 (D-K03): `@xlvane/compat` `xlsx.load` limits merged cells to `max(1 048 576, cells in the file)` (`details.limit` `mergedCells`; each merged position becomes a cell, which a row or column style gives a style, so a few bytes of `mergeCell` could ask for millions of styled cells), and `csv.read` stops at more than 1 048 576 rows (`details.limit` `maxRows`) 16 384 fields in a row (`details.limit` `maxColumns`, with `location.line`) or 5 000 000 fields in all (`details.limit` `maxCells`) while it parses.
116
116
 
117
117
  The MCP server (`@xlvane/mcp`) returns it as a tool error for a file larger than `--max-file-size` (`details.limit` `maxFileBytes`; `convert` to JSON stops building the text at that size), for a workbook with more cells than `--max-cells` (`details.limit` `maxCells`) and for a result longer than `--max-response-chars` (`details.limit` `maxResponseChars`; `read_table` instead returns the rows that fit). `xlvane convert` to JSON returns it when the text would be longer than a JavaScript string can be.
118
118
 
@@ -350,7 +350,7 @@ A sheet defines the same cell more than once. The last definition is used, also
350
350
  **Fix:** nothing, if the last definition is right; otherwise re-save the file in the application that made it.
351
351
 
352
352
  ### XLV_W_INVALID_VALUE
353
- A value does not match its declared type and is returned in a safe form, for example a numeric cell whose text is not a number (returned as text), a boolean cell that is neither 0 nor 1, a shared-string index that does not exist (the cell is treated as empty), a date serial outside the date system (returned as a number), an unknown sheet state (treated as visible), or an invalid style index or range. The message says what was found and what was done. A chart is skipped with this warning when its part is not well-formed XML, is not encoded in UTF-8 or UTF-16, has no chart with a plot area, or when the chart's relationship leads to a part whose content type is not the chart content type (D-K01).
353
+ A value does not match its declared type and is returned in a safe form, for example a numeric cell whose text is not a number (returned as text), a boolean cell that is neither 0 nor 1, a shared-string index that does not exist (the cell is treated as empty), a date serial outside the date system (returned as a number), an unknown sheet state (treated as visible), a colour `rgb` of 6 hex digits instead of the 8 of an ARGB value (read as an opaque RGB colour, `FF` followed by the 6 digits; docs/facts/sml-write.md W2-16a), or an invalid style index or range. The message says what was found and what was done. A chart is skipped with this warning when its part is not well-formed XML, is not encoded in UTF-8 or UTF-16, has no chart with a plot area, or when the chart's relationship leads to a part whose content type is not the chart content type (D-K01).
354
354
  **Fix:** read the message for the cell and the value used; re-save the file in the application that made it to repair the data.
355
355
 
356
356
  ### XLV_W_UNUSUAL_HYPERLINK
@@ -12,7 +12,7 @@ npm installs an alias `<alias>@npm:<name>` under the alias name in `node_modules
12
12
 
13
13
  - **ESM:** `import ExcelJS from 'exceljs'` works as with exceljs. `@xlvane/compat` is an ES module, so named imports (`import { Workbook } from 'exceljs'`) work too; exceljs 4.4.0 rejects them because it is a CommonJS package.
14
14
  - **CommonJS:** `const ExcelJS = require('exceljs')` needs **Node.js 22.12 or later**. The package is ESM-only and exports the `ExcelJS` object under the name `"module.exports"`, which `require()` of an ES module returns directly from Node.js 22.12 on (Node.js `doc/api/modules.md`, "Loading ECMAScript modules using `require()`"). `engines.node` is `>=22.12`.
15
- - **Unsupported API** throws an `XlvError` with code `XLV_E_NOT_SUPPORTED`, naming the method (docs/errors.md). A few options and settings are still ignored without an error; they are listed under [Known differences planned for 1.3.1](#known-differences-planned-for-131).
15
+ - **Unsupported API** throws an `XlvError` with code `XLV_E_NOT_SUPPORTED`, naming the method (docs/errors.md). Options and settings that are accepted but not handled as exceljs handles them are described with the API they belong to (for example `formatterOptions.objectMode` of `csv.write` has no effect); the [Known differences from the 1.3.0 review](#known-differences-from-the-130-review) lists further differences in values.
16
16
 
17
17
  Status: **supported** means the same behaviour as exceljs for the cases covered by the ported tests (`tests/exceljs/`). **Partly** means supported with the difference given in the notes. **Not supported** means it throws `XLV_E_NOT_SUPPORTED`. The source column points to the exceljs 4.4.0 implementation (paths relative to the package root, as installed in `bench/node_modules/exceljs`).
18
18
 
@@ -30,15 +30,15 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
30
30
  | `views` (workbook windows) | supported | Every view is written as a `workbookView` with exceljs's defaults (`x` 0, `y` 0, `width` 12000, `height` 24000) and `firstSheet`, `activeTab`, `visibility` when given; read back with them (X4-28). | `lib/doc/workbook.js` L28, L164; `lib/xlsx/xform/book/workbook-view-xform.js` |
31
31
  | `calcProperties` (`fullCalcOnLoad`) | partly | `fullCalcOnLoad: true` is written as `calcPr fullCalcOnLoad="1"` and read back. exceljs always writes `calcPr calcId="171027"`; xlvane writes `calcPr` only when `fullCalcOnLoad` is set and never a `calcId` (sml.xsd CT_CalcPr makes it optional; xlvane does not calculate, so it claims no calculation engine version; the meaning of `calcId` is not checked against Part 1 prose, PROVISIONAL). `calcProperties` reads back as `{ fullCalcOnLoad: true }` or `{}`; exceljs reads `{}` in both cases. | `lib/doc/workbook.js` L24, L177; `lib/xlsx/xform/book/workbook-calc-properties-xform.js` |
32
32
  | `model` (get / set), `nextId`, `clearThemes()` | supported | `model` is a plain snapshot of the workbook that can be set on another workbook (it holds xlvane's worksheet models, not exceljs's internal xform models). `clearThemes()` does nothing: xlvane never keeps or writes a theme part. | `lib/doc/workbook.js` L43, L137, L153–215 |
33
- | `xlsx.load(buffer, options)` | supported | Reading goes through xlvane's limits for untrusted files. Options as in exceljs (X4-25): `base64` reads base64 text; `ignoreNodes` skips worksheet elements by name (`sheetPr`, `cols`, `sheetData`, `sheetViews`, `autoFilter`, `mergeCells`, `hyperlinks`, `dataValidations`, `conditionalFormatting`, `sheetProtection`, `pageSetup`, `headerFooter`, `rowBreaks`, `drawing`, `picture`, `tableParts`); `maxRows` and `maxCols` refuse bigger worksheets with `XLV_E_LIMIT_EXCEEDED` (exceljs throws a plain `Error`). The same options apply to `readFile` and `read`. Merged ranges may cover at most max(1 048 576, cells in the file) positions in total (one whole column fits); more throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `mergedCells`), because each merged cell carries the master's style and is written as a styled cell, so the cost of `writeBuffer` grows with the merged positions: a merged range whose cells all carry the master's style is styled in blocks of rows (K2), also when some of its cells have notes (K3), and even so 1 048 575 merged positions took about 3.5 s and 750 MB (peak resident memory) to write (measured with Node.js 22.23, K3). `ignoreNodes: ["mergeCells"]` skips them. | `lib/xlsx/xlsx.js` L257; `lib/xlsx/xform/sheet/worksheet-xform.js` L95, L372 |
33
+ | `xlsx.load(buffer, options)` | supported | Reading goes through xlvane's limits for untrusted files. Options as in exceljs (X4-25): `base64` reads base64 text; `ignoreNodes` skips worksheet elements by name (`sheetPr`, `cols`, `sheetData`, `sheetViews`, `autoFilter`, `mergeCells`, `hyperlinks`, `dataValidations`, `conditionalFormatting`, `sheetProtection`, `pageSetup`, `headerFooter`, `rowBreaks`, `drawing`, `picture`, `tableParts`); `maxRows` and `maxCols` refuse bigger worksheets with `XLV_E_LIMIT_EXCEEDED` (exceljs throws a plain `Error`). The same options apply to `readFile` and `read`. Merged ranges may cover at most max(1 048 576, cells in the file) positions in total (one whole column fits); more throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `mergedCells`), because each merged position becomes a cell, which takes the row and column style, and a cell with a style is written as a styled cell, so the cost of `writeBuffer` grows with the merged positions: the rows of a merged range whose cells all carry the master's style (or an equal one, for example from a row or column style) are styled in blocks (K2), also when some of its cells have notes (K3), and even so 1 048 575 merged positions took about 3.5 s and 750 MB (peak resident memory) to write (measured with Node.js 22.23, K3). `ignoreNodes: ["mergeCells"]` skips them. As in exceljs, the ranges are merged without styles (`mergeCellsWithoutStyle`), so each cell of a merged range keeps its own style from the file, and a cell the file leaves out gets the row and column style. | `lib/xlsx/xlsx.js` L257; `lib/xlsx/xform/sheet/worksheet-xform.js` L95, L372; `lib/doc/worksheet.js` L895-901 |
34
34
  | `xlsx.readFile(path)` | supported | | `lib/xlsx/xlsx.js` L49 |
35
35
  | `xlsx.read(stream)` | supported | | `lib/xlsx/xlsx.js` L244 |
36
36
  | `xlsx.writeBuffer(options)` | supported | `useStyles: false` writes no cell or column styles, dates keeping format 14, as exceljs. `useSharedStrings: false` writes inline strings and no shared string table; `zip.compression: 'STORE'` stores every part, `zip.compressionOptions.level` (0–9) sets the deflate level; other `zip` options are ignored (X4-25). The output stays deterministic. The same options apply to `writeFile` and `write`. | `lib/xlsx/xlsx.js` L602–603, L685 |
37
37
  | `xlsx.writeFile(path)` | supported | | `lib/xlsx/xlsx.js` L666 |
38
38
  | `xlsx.write(stream)` | supported | | `lib/xlsx/xlsx.js` L643 |
39
39
  | `xlsx.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/xlsx/xlsx.js` L238 |
40
- | `csv.read(stream, options)`, `csv.readFile(path, options)` | supported | Adds a worksheet, with exceljs's options (`sheetName`, `dateFormats`, `map`, `parserOptions`: `delimiter`, `quote`, `escape`, `trim`/`ltrim`/`rtrim`, `ignoreEmpty`, `comment`, `skipLines`, `skipRows`, `maxRows`, `headers`, `renameHeaders`, `encoding`; `latin1` is decoded as windows-1252, F10 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)) and its default conversion of fields (X4-23), on xlvane's own parser (no fast-csv). Accepts a Node.js readable stream, a Web `ReadableStream` or any (async) iterable of strings or bytes. Differences: text of white space only stays text (exceljs reads `' '` as 0), `-Infinity` stays text (it cannot be stored), a leading byte order mark is dropped, as in exceljs (fast-csv 4.3.6 removes it: `@fast-csv/parse/build/src/parser/Parser.js` L12-19 `removeBOM`, called by `parse` at L20-23), `headers: true` skips the header line (exceljs throws `data.map is not a function`), a missing file or a malformed line throws `XLV_E_FILE_READ` / `XLV_E_CSV_MALFORMED`. `readFile` needs `node:fs` (Node.js, Bun, Deno). Dates are parsed as dayjs 1.11 customParseFormat parses them in strict mode (D-E06): letters that are no token, such as an unbracketed `T` or `dddd`, are skipped, and each token is searched in the rest of the text. An unclosed quote is reported on the line where it opens; a `quote` or `escape` longer than one character throws `XLV_E_INVALID_OPTION`. Rows are added as they are parsed, as fast-csv streams them, so a malformed line leaves the rows before it in the worksheet. More than 1 048 576 rows, or a row of more than 16 384 fields, throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxRows` or `maxColumns`) as soon as parsing reaches it (exceljs collects the rows and fails with a plain `Error` in `addRow`); rows left out by `ignoreEmpty`, the header, `skipRows` and `maxRows` do not count (K2). More than 5 000 000 fields in the rows added (`DEFAULT_READ_LIMITS.maxCells`, the core's cell limit) throw `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxCells`, with the line) before any row is added: a text of 5 000 000 characters or more is counted in a first pass (K3; exceljs has no such limit, and 10 MB of `1,1,…` took 885 MB). | `lib/csv/csv.js` L32–98 |
41
- | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`) and fast-csv's quoting (X4-24); `transform`, `quoteHeaders` and `quoteColumns` given as an object are ignored (F9 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)); the cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10, CSV-12; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`; with `latin1` characters above U+00FF are written as `?` (Node.js, and so exceljs, keeps only their low byte, which can turn U+013D into `=`), and formatter options with such characters throw `XLV_E_INVALID_OPTION`. While formulas are escaped (the default), a `delimiter` holding a formula sign (`=`, `+`, `-`, `@`, their full-width or small forms) or a control character (a lone tab is allowed), a `rowDelimiter` other than `\n`, `\r\n` or `\r`, a `quote` other than `"` or `false`, an `escape` other than the quote, and with `quote: false` a field that starts with `"` throw `XLV_E_INVALID_OPTION`, because a standard CSV reader would otherwise get a field starting with a formula (docs/facts/csv.md CSV-13); `formulaInjection: "off"` writes them as exceljs does. Also while formulas are escaped (K2): text that starts with a byte order mark (U+FEFF or U+FFFE) is escaped, because applications drop a BOM at the start of a file and run what follows; with `encoding: "latin1"` or `"utf16le"` a field is also escaped when its first bytes as written are a BOM (EF BB BF, FF FE, FE FF, for example the latin1 text `=1+1`) or the UTF-8 form of a full-width or small formula sign, and with `latin1` and `writeBOM` (written as the byte FF) a first field starting with `þ`; a value from `map` that is not a number, boolean, `null` or `undefined` (a `String` object, an array, an object with `toString`) is turned into its text once and checked like text (fast-csv writes its text unchecked); a field holding a comma, `;`, tab or `|` is quoted whatever the delimiter (fast-csv quotes only the delimiter and `|`), because an application that splits on another of these characters would see a new field there, and with `quote: false` a field where one of them is followed by a formula sign (after spaces and an opening `"`) throws `XLV_E_INVALID_OPTION`. Also while formulas are escaped (K3): an empty `delimiter` throws `XLV_E_INVALID_OPTION`, and so does, with `latin1` or `utf16le`, a `delimiter` or `rowDelimiter` with characters other than ASCII, because the bytes of a field could otherwise join the next field's (or the delimiter's) into a byte order mark or a formula sign. `utf16le` output starts with the UTF-16LE byte order mark FF FE (exceljs writes it only with `writeBOM: true`). LibreOffice 24.2.7.2 reads a UTF-16LE file as 8-bit text and drops the NUL bytes, with its default import and with charset UTF-8, also after that mark (only charset 65535, UTF-16, decodes it), and starts a formula only at a field beginning with `=` (or a quote and `=`); so with `utf16le` a field whose bytes without NULs begin so (for example `㴀㄀⬀㄀`, bytes 00 3D 00 31 …, or `丽`, 3D 4E) is escaped with `'`, and a field whose bytes there hold a delimiter (`,`, `;`, tab, `|`) or line break followed by `=`, through a character above U+00FF (`ਗ=1`, 17 0A …) or after a quote byte of one (`a∀,=1`; such a quote also counts for all later fields), throws `XLV_E_INVALID_OPTION` (`details.option` `encoding`, with the cell); CJK text whose bytes hold such characters without `=` after them is written unchanged. The mark's bytes come before the first field in that reading, so its opening quote is not taken as quoting there: a quoted first field holding a delimiter or line break followed by `=` (`x,=1+1`) throws `XLV_E_INVALID_OPTION` with `utf16le`, and with `latin1` and `writeBOM` (the byte FF); with UTF-8, whose mark LibreOffice drops, it is written. | `lib/csv/csv.js` L109–186 |
40
+ | `csv.read(stream, options)`, `csv.readFile(path, options)` | supported | Adds a worksheet, with exceljs's options (`sheetName`, `dateFormats`, `map`, `parserOptions`: `delimiter`, `quote`, `escape`, `trim`/`ltrim`/`rtrim`, `ignoreEmpty`, `comment`, `skipLines`, `skipRows`, `maxRows`, `headers`, `renameHeaders`, `encoding`; `latin1` and `binary` map each byte to the code point of the same value, as Node.js's `StringDecoder` in fast-csv does, `@fast-csv/parse/build/src/CsvParserStream.js` L20) and its default conversion of fields (X4-23), on xlvane's own parser (no fast-csv). As with fast-csv, a line of white space only is a row without fields, for which `map` is not called (`@fast-csv/parse/build/src/parser/RowParser.js` L15-48). Accepts a Node.js readable stream, a Web `ReadableStream` or any (async) iterable of strings or bytes. Differences: text of white space only stays text (exceljs reads `' '` as 0), `-Infinity` stays text (it cannot be stored), a leading byte order mark is dropped, as in exceljs (fast-csv 4.3.6 removes it: `@fast-csv/parse/build/src/parser/Parser.js` L12-19 `removeBOM`, called by `parse` at L20-23), `headers: true` skips the header line (exceljs throws `data.map is not a function`), a missing file or a malformed line throws `XLV_E_FILE_READ` / `XLV_E_CSV_MALFORMED`. `readFile` needs `node:fs` (Node.js, Bun, Deno). Dates are parsed as dayjs 1.11 customParseFormat parses them in strict mode (D-E06): letters that are no token, such as an unbracketed `T` or `dddd`, are skipped, and each token is searched in the rest of the text. An unclosed quote is reported on the line where it opens; a `quote` or `escape` longer than one character throws `XLV_E_INVALID_OPTION`. Rows are added as they are parsed, as fast-csv streams them, so a malformed line leaves the rows before it in the worksheet. More than 1 048 576 rows, or a row of more than 16 384 fields, throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxRows` or `maxColumns`) as soon as parsing reaches it (exceljs collects the rows and fails with a plain `Error` in `addRow`); rows left out by `ignoreEmpty`, the header, `skipRows` and `maxRows` do not count (K2). More than 5 000 000 fields in the rows added (`DEFAULT_READ_LIMITS.maxCells`, the core's cell limit) throw `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxCells`, with the line) before any row is added: a text of 5 000 000 characters or more is counted in a first pass (K3; exceljs has no such limit, and 10 MB of `1,1,…` took 885 MB). | `lib/csv/csv.js` L32–98 |
41
+ | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns` and `quoteHeaders` (`true`, an array, or an object of header names), `headers` (an array or `true`), `writeHeaders`, `alwaysWriteHeaders`, `includeEndRowDelimiter`, `writeBOM`, `transform`) and fast-csv's quoting and row handling (X4-24; `@fast-csv/format` 4.3.5 `build/src/formatter/RowFormatter.js`, `FieldFormatter.js`): `transform` gets each row (the mapped values, `[]` for a missing row) and may return an array, an object (written by the header names), a list of `[header, value]` pairs or nothing (no line); a transform that does not take exactly one parameter is called with a callback, as fast-csv does; the header line is written with the first row (none for a worksheet without rows, unless `alwaysWriteHeaders`); `headers: true` takes the worksheet's first row as the header line, and the blank lines for missing rows before it are left out (exceljs writes one fewer blank line before the first row, see below, so from a first row of 3 on its blank line becomes the header line and no data is written). Formulas are escaped in the fields that `transform` returns as in all others. `BOM` other than U+FEFF throws `XLV_E_NOT_SUPPORTED`; `objectMode` has no effect. The cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10, CSV-12; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`; with `latin1` characters above U+00FF are written as `?` (Node.js, and so exceljs, keeps only their low byte, which can turn U+013D into `=`), and formatter options with such characters throw `XLV_E_INVALID_OPTION`. While formulas are escaped (the default), a `delimiter` holding a formula sign (`=`, `+`, `-`, `@`, their full-width or small forms) or a control character (a lone tab is allowed), a `rowDelimiter` other than `\n`, `\r\n` or `\r`, a `quote` other than `"` or `false`, an `escape` other than the quote, and with `quote: false` a field that starts with `"` throw `XLV_E_INVALID_OPTION`, because a standard CSV reader would otherwise get a field starting with a formula (docs/facts/csv.md CSV-13); `formulaInjection: "off"` writes them as exceljs does. Also while formulas are escaped (K2): text that starts with a byte order mark (U+FEFF or U+FFFE) is escaped, because applications drop a BOM at the start of a file and run what follows; with `encoding: "latin1"` or `"utf16le"` a field is also escaped when its first bytes as written are a BOM (EF BB BF, FF FE, FE FF, for example the latin1 text `=1+1`) or the UTF-8 form of a full-width or small formula sign, and with `latin1` and `writeBOM` (written as the byte FF) a first field starting with `þ`; a value from `map` that is not a number, boolean, `null` or `undefined` (a `String` object, an array, an object with `toString`) is turned into its text once and checked like text (fast-csv writes its text unchecked); a field holding a comma, `;`, tab or `|` is quoted whatever the delimiter (fast-csv quotes only the delimiter and `|`), because an application that splits on another of these characters would see a new field there, and with `quote: false` a field where one of them is followed by a formula sign (after spaces and an opening `"`) throws `XLV_E_INVALID_OPTION`. Also while formulas are escaped (K3): an empty `delimiter` throws `XLV_E_INVALID_OPTION`, and so does, with `latin1` or `utf16le`, a `delimiter` or `rowDelimiter` with characters other than ASCII, because the bytes of a field could otherwise join the next field's (or the delimiter's) into a byte order mark or a formula sign. `utf16le` output starts with the UTF-16LE byte order mark FF FE (exceljs writes it only with `writeBOM: true`). LibreOffice 24.2.7.2 reads a UTF-16LE file as 8-bit text and drops the NUL bytes, with its default import and with charset UTF-8, also after that mark (only charset 65535, UTF-16, decodes it), and starts a formula only at a field beginning with `=` (or a quote and `=`); so with `utf16le` a field whose bytes without NULs begin so (for example `㴀㄀⬀㄀`, bytes 00 3D 00 31 …, or `丽`, 3D 4E) is escaped with `'`, and a field whose bytes there hold a delimiter (`,`, `;`, tab, `|`) or line break followed by `=`, through a character above U+00FF (`ਗ=1`, 17 0A …) or after a quote byte of one (`a∀,=1`; such a quote also counts for all later fields), throws `XLV_E_INVALID_OPTION` (`details.option` `encoding`, with the cell); CJK text whose bytes hold such characters without `=` after them is written unchanged. The mark's bytes come before the first field in that reading, so its opening quote is not taken as quoting there: a quoted first field holding a delimiter or line break followed by `=` (`x,=1+1`) throws `XLV_E_INVALID_OPTION` with `utf16le`, and with `latin1` and `writeBOM` (the byte FF); with UTF-8, whose mark LibreOffice drops, it is written. | `lib/csv/csv.js` L109–186 |
42
42
  | `csv.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/csv/csv.js` L101 |
43
43
  | `definedNames` (`add`, `addEx`, `remove`, `removeEx`, `removeAllNames`, `forEach`, `getNames`, `getNamesEx`, `getRanges`, `getMatrix`, `model`) | supported | As in exceljs, a name is a set of cells, written as rectangles with absolute references and no sheet scope; on reading, names that are not cell ranges (constants, formulas, `#REF!`) and the sheet scope (`localSheetId`) are dropped. Built-in `_xlnm.` names are not listed: print areas and titles are read into `pageSetup`, as in exceljs, and xlvane writes the autofilter name itself. Names exceljs accepts but Excel does not (for example `A1` or names starting with `_xl`) throw `XLV_E_INVALID_DEFINED_NAME` when the workbook is written. | `lib/doc/defined-names.js`; `lib/xlsx/xform/book/defined-name-xform.js`; `lib/xlsx/xform/book/workbook-xform.js` L187 |
44
44
  | `addImage(image)`, `getImage(id)` | supported | PNG, JPEG and GIF given as `buffer`, `base64` (a data URL is accepted, as in exceljs) or `filename`; the format is recognised from the bytes, `extension` is kept but not used (exceljs writes `image/jpg` for `extension: 'jpg'`). As in exceljs, a `filename` is read when the workbook is written (`xlsx.writeBuffer`/`writeFile`/`write` and the streaming `WorkbookWriter`), so a missing file fails there; outside Node.js, Bun and Deno it gives `XLV_E_NOT_SUPPORTED`. Other formats throw `XLV_E_INVALID_IMAGE`. Read back, each image part is one media entry `{ type, name, extension, buffer }`, `buffer` a `Uint8Array`. Identical images are stored once, with one relationship per drawing (exceljs writes one per use). | `lib/doc/workbook.js` L142–151; `lib/xlsx/xlsx.js` L421–442 (L426–429: `filename` read at write) |
@@ -56,12 +56,12 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
56
56
  | `eachRow({ includeEmpty }, cb)` | supported | | `lib/doc/worksheet.js` L551 |
57
57
  | `getSheetValues()` | supported | Every row object, also an empty one (`[]`), as in exceljs. | `lib/doc/worksheet.js` L571–579 |
58
58
  | `rowCount`, `actualRowCount`, `columnCount` | supported | | `lib/doc/worksheet.js` L374, L378, L313 |
59
- | `mergeCells(range \| t, l, b, r)`, `unMergeCells` | partly | Styles of merged cells differ, see F1 and F2 under [Known differences planned for 1.3.1](#known-differences-planned-for-131). | `lib/doc/worksheet.js` L602, L656 |
59
+ | `mergeCells(range \| t, l, b, r)`, `unMergeCells` | supported | As in exceljs, the other cells of the range share the top-left cell's style object, so a style set on any cell of the range afterwards (`cell.fill = ...`) shows on all of them and is written for all of them; assigning `cell.style` gives one cell an object of its own. `unMergeCells` gives the other cells the row and column style again. | `lib/doc/worksheet.js` L602, L656; `lib/doc/cell.js` L169-183 |
60
60
  | `mergeCellsWithoutStyle(...)`, `findRows(start, length)`, `lastColumn`, `fillFormula(range, formula, results, shareType)`, `model` (get / set), `destroy()` | supported | `fillFormula` writes the formula into the top-left cell and `{ sharedFormula }` (or the same array formula) into the others, with results from a function, a flat array or a 2-D array, as in exceljs (X4-29). `findRows` returns `_rows.slice(start - 1, start - 1 + length)` as exceljs does (D-E06). `model` is xlvane's own plain snapshot (see `workbook.model`). | `lib/doc/worksheet.js` L181, L309, L370, L607, L678–721, L842–903 |
61
- | `views` | partly | The first view is written: frozen panes, split panes (`xSplit`/`ySplit` in 1/20 point), `zoomScale` (10–400), `zoomScaleNormal`, `showGridLines`, `showRowColHeaders`, `showRuler`, `rightToLeft`, `style` (`pageBreakPreview`, `pageLayout`), `activeCell`, and `topLeftCell` of a normal view (X4-28). Differences: further views are not written, because xlvane's core writes one `sheetView` per worksheet; `workbookViewId` is always 0; frozen panes always get the cell right of and below the panes as `topLeftCell` (exceljs writes a frozen view's own `topLeftCell` when given, sheet-view-xform.js L50-53); a split view's `topLeftCell` is written as `pane@topLeftCell` and read back, as in exceljs (L72-85, L118-124; D-F02), and its `activeCell` is written and read back as the selection of the active pane; `activePane` is the pane the split implies; `tabSelected` is written for the first visible worksheet, whatever the view says. Every worksheet reads back with one view shaped as exceljs reads it. | `lib/doc/worksheet.js` L114; `lib/xlsx/xform/sheet/sheet-view-xform.js`; `index.d.ts` L33–149 |
61
+ | `views` | partly | The first view is written: frozen panes, split panes (`xSplit`/`ySplit` in 1/20 point), `zoomScale` (10–400), `zoomScaleNormal`, `showGridLines`, `showRowColHeaders`, `showRuler`, `rightToLeft`, `style` (`pageBreakPreview`, `pageLayout`), `activeCell`, and `topLeftCell` of a normal view (X4-28). Differences: further views are not written, because xlvane's core writes one `sheetView` per worksheet; `workbookViewId` is always 0; frozen panes always get the cell right of and below the panes as `topLeftCell` (exceljs writes a frozen view's own `topLeftCell` when given, sheet-view-xform.js L50-53), and a file's `pane@topLeftCell` of frozen panes is not read (exceljs reads it, L157); a frozen view whose active cell is that cell right of and below the panes reads without `activeCell`, because xlvane writes that cell as the active cell when none is given (exceljs writes none then, L65-69, and reads the active pane's selection, L167-170); a split view's `topLeftCell` is written as `pane@topLeftCell` and read back, as in exceljs (L72-85, L118-124; D-F02), and its `activeCell` is written and read back as the selection of the active pane; `activePane` is the pane the split implies; `tabSelected` is written for the first visible worksheet, whatever the view says. Every worksheet reads back with one view shaped as exceljs reads it. | `lib/doc/worksheet.js` L114; `lib/xlsx/xform/sheet/sheet-view-xform.js`; `index.d.ts` L33–149 |
62
62
  | `autoFilter` (range string) | supported | | `lib/doc/worksheet.js` L116 |
63
63
  | `insertRow(pos, values, style?)`, `insertRows(pos, values, style?)` | partly | Style options `n`, `i`, `i+`, `o`, `o+` as in exceljs. Built on `spliceRows`, so the differences below apply. | `lib/doc/worksheet.js` L422, L428, L444 |
64
- | `spliceRows(start, count, ...inserts)` | partly | xlvane moves rows as a spreadsheet application does; exceljs 4.4.0 moves only values, styles and heights (X4-15). Differences: row objects keep their identity and their `number` follows them (exceljs copies values between row objects); the hidden flag, notes and links move with the row (exceljs leaves hidden/outline at the position and drops notes); merged ranges move, grow when rows are inserted inside them (inserted cells get a copy of the top-left cell's style, the other cells keep their own), shrink when rows are removed, and are dropped when their top-left cell is removed or one cell or nothing is left, as LibreOffice does (R-14; exceljs keeps stale ranges and dissolves merges on removal); the autofilter range moves and shrinks, and stops at the last row or column (exceljs leaves it); cells inserted inside a defined name join it (exceljs leaves a gap), and names follow whatever the case of their sheet name (exceljs compares it exactly); styles are copied, never shared; removing more rows than follow the removed block works (exceljs leaves rows behind). Formulas in every worksheet follow the splice as in a spreadsheet application (xlvane `adjustFormula`, D-B01; exceljs leaves them unchanged); formulas given in the inserted rows keep the text the caller wrote. If a formula cannot be adjusted safely, the call throws `XLV_E_NOT_SUPPORTED` before anything changes. | `lib/doc/worksheet.js` L483–548 |
64
+ | `spliceRows(start, count, ...inserts)` | partly | xlvane moves rows as a spreadsheet application does; exceljs 4.4.0 moves only values, styles and heights (X4-15). Differences: row objects keep their identity and their `number` follows them (exceljs copies values between row objects); the hidden flag, notes and links move with the row (exceljs leaves hidden/outline at the position and drops notes); merged ranges move, grow when rows are inserted inside them (inserted cells get a copy of the top-left cell's style, the other cells keep their own), shrink when rows are removed, and are dropped when their top-left cell is removed or one cell or nothing is left, as LibreOffice does (R-14; exceljs keeps stale ranges and dissolves merges on removal); the autofilter range moves and shrinks, and stops at the last row or column (exceljs leaves it); cells inserted inside a defined name join it (exceljs leaves a gap), and names follow whatever the case of their sheet name (exceljs compares it exactly); styles are copied, never shared (the cells of a merged range keep the style object they share with the top-left cell, see `mergeCells`); removing more rows than follow the removed block works (exceljs leaves rows behind). Formulas in every worksheet follow the splice as in a spreadsheet application (xlvane `adjustFormula`, D-B01; exceljs leaves them unchanged); formulas given in the inserted rows keep the text the caller wrote. If a formula cannot be adjusted safely, the call throws `XLV_E_NOT_SUPPORTED` before anything changes. | `lib/doc/worksheet.js` L483–548 |
65
65
  | `duplicateRow(rowNum, count, insert = false)` | partly | The default of `insert` is `false`, as in exceljs's code (its README says `true`). Copies get the values, styles (copied, not shared) and height; merged ranges inside the row are copied too; a merged range's other cells stay merged instead of getting the master value (exceljs); a formula is copied as a spreadsheet application copies a cell: its relative references move by the distance of the copy (xlvane `shiftFormula`; exceljs copies the text). With `insert = true` the rows below move first and every formula follows (`spliceRows`), so a copy of `A5` in row 1 reads `A6` in row 1 and `A7` in row 2. With `insert = false` the rows below are overwritten in place: merged ranges that touch them are removed and no other formula changes (exceljs implements this as a removal plus an insertion). | `lib/doc/worksheet.js` L463 |
66
66
  | `spliceColumns(start, count, ...inserts)` | partly | Each insert is an array of values by row (index 0 is row 1). Columns move with their cells, widths, styles, keys and notes; merged ranges, the autofilter, defined names and formulas follow, as for `spliceRows`. Unlike exceljs, falsy values such as `0` and `false` are kept, values beyond the existing rows are written, headers are not written again into row 1, and the last column does not get width 9. | `lib/doc/worksheet.js` L264–307 |
67
67
  | Invalid arguments of the row and column methods | partly | Arguments exceljs accepts and then loses or misplaces data with (`insertRow(0)`, `insertRow('2')`, a negative count, `duplicateRow` of a missing row or without a count, `insertRows` without an array, rows or columns pushed, or inserted, beyond row 1048576 or column XFD) throw `XLV_E_INVALID_ARGUMENT` before anything changes. | `lib/doc/worksheet.js` L422–548 |
@@ -82,7 +82,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
82
82
  | `eachCell({ includeEmpty }, cb)` | supported | As in exceljs, cells of a merged range give the master cell's value. | `lib/doc/row.js` L130 |
83
83
  | `height`, `hidden` | supported | | `lib/doc/row.js` L310 |
84
84
  | `outlineLevel`, `collapsed`, `protection`, `getCellEx(address)` | supported | `outlineLevel` (0–7, [MS-OI29500] §2.1.644) is written and read back; a level outside 0–7 throws `XLV_E_INVALID_ARGUMENT` when it is set (exceljs writes it; D-E06); `collapsed` is derived from the level and `properties.outlineLevelRow`, as in exceljs (X4-28). | `lib/doc/row.js` L45, L286–330 |
85
- | `font`, `fill`, `border`, `alignment`, `numFmt` setters | partly | Apply to the row's cells. The row's own style is not written or read (F3 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)). | `lib/doc/row.js` L274, L306 |
85
+ | `font`, `fill`, `border`, `alignment`, `numFmt` setters | supported | Apply to the row's existing cells and to `row.style`, from which cells created later in the row take their style, as in exceljs. `row.style` is written as `<row s="..." customFormat="1">` for the rows exceljs writes, those with a height or cells (a row with only a style is not written, `lib/doc/row.js` L333-364; xlvane also writes hidden and outlined rows), and only with styles (not with `useStyles: false`); the streaming writer writes it too. `xlsx.load`, and the streaming reader with `styles: 'cache'`, read it back as `row.style`. Difference: a row's `s` is read only when the row has `customFormat="1"`, without which it does not apply (ECMA-376 Part 1 §18.3.1.73); exceljs reads `s` without that check (`row-xform.js` L80-82). `xlsx.load` reads row settings only up to the sheet's dimension or last cell (see [Writing workbooks that were read from files](#writing-workbooks-that-were-read-from-files)). | `lib/doc/row.js` L252-306; `lib/xlsx/xform/sheet/row-xform.js` L19-23, L44-47, L80-82, L130 |
86
86
  | `commit()` | supported | Meaningful for `WorkbookWriter`; does nothing in a normal workbook, as in exceljs. | `lib/doc/row.js` L29 |
87
87
  | `cellCount`, `actualCellCount` | supported | | `lib/doc/row.js` L216, L220 |
88
88
  | `splice(start, count, ...inserts)` | partly | Cells move with their values, styles and notes, as in exceljs. A row that crosses a merged range at or right of `start` throws `XLV_E_NOT_SUPPORTED` (the range cannot follow the cells of one row; exceljs gives wrong values). Removed cells at the end are removed, not set to `null`. Formulas are not rewritten, as in exceljs. | `lib/doc/row.js` L77–127 |
@@ -96,17 +96,17 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
96
96
  | `outlineLevel`, `collapsed`, `protection`, `isDefault`, `toString()`, `equivalentTo(other)` | supported | `outlineLevel` (0–7) is written and read back, and a level outside 0–7 throws `XLV_E_INVALID_ARGUMENT` when it is set (D-E06); `collapsed` is derived from the level and `properties.outlineLevelCol`, as in exceljs (X4-28). | `lib/doc/column.js` L116–147, L238 |
97
97
  | `letter`, `number` | supported | | `lib/doc/column.js` L23, L31 |
98
98
  | `values` | supported | | `lib/doc/column.js` L179 |
99
- | `eachCell({ includeEmpty }, cb)` | partly | Without `includeEmpty`, only cells with values are visited (F12 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)). | `lib/doc/column.js` L168 |
100
- | `font`, `fill`, `border`, `alignment`, `numFmt` setters, `style` | supported | Applied to the column's existing cells, inherited by cells added later (the row style wins), and written as the column's default style (`col@style`). As in exceljs, a styled column without a width is written with width 9, and a column with width 9 and nothing else is not written. The streaming writer writes the column style only with `useStyles: true`. | `lib/doc/column.js` L147, L206, L265 |
99
+ | `eachCell({ includeEmpty }, cb)` | supported | As in exceljs, the column's cell of each row that `worksheet.eachRow` visits (the rows with values, or every row up to the last with `includeEmpty`), created when it does not exist yet. | `lib/doc/column.js` L168-177 |
100
+ | `font`, `fill`, `border`, `alignment`, `numFmt` setters, `style` | supported | Applied, as in exceljs, to the column's cell of each row with values (created when needed; `lib/doc/column.js` L206-212 through `eachCell`), inherited by cells added later (the row style wins), and written as the column's default style (`col@style`). As in exceljs, a styled column without a width is written with width 9, and a column with width 9 and nothing else is not written. The streaming writer writes the column style only with `useStyles: true`. | `lib/doc/column.js` L147, L206, L265 |
101
101
 
102
102
  ## Cell values
103
103
 
104
104
  | Value | Status | Notes | Source in exceljs |
105
105
  |---|---|---|---|
106
106
  | `null`, number, string, boolean | supported | | `lib/doc/cell.js` L206, L211 |
107
- | `Date` | partly | Without `numFmt`, a date (also a formula result that is a date) is written with built-in format 14, as exceljs does, also by the streaming writer without `useStyles`; applications show it in the reader's date order, and reading gives `numFmt` `"mm-dd-yy"`. Time-only values, dates before 1900-03-01, upper-case date formats and dates outside 1900-9999 differ, see F6, F7 and F8 under [Known differences planned for 1.3.1](#known-differences-planned-for-131). | `lib/doc/cell.js` L538; `lib/xlsx/xform/style/styles-xform.js` L253–266, L498–515 |
107
+ | `Date` | partly | Without `numFmt`, a date (also a formula result that is a date) is written with built-in format 14, as exceljs does, also by the streaming writer without `useStyles`; applications show it in the reader's date order, and reading gives `numFmt` `"mm-dd-yy"`. A time-only value given as exceljs does (`new Date(Date.UTC(1899, 11, 30, 13, 20))`) is written as exceljs writes it, serial 0.5555... (`lib/utils/utils.js` L55-57 `dateToExcel`), and a serial below 1 with a date format reads as 1899-12-30 plus the time (L58-61 `excelToDate`). A number reads as a `Date` when exceljs takes its format for a date format (L140-151 `isDateFmt`: one of the letters y, m, d, h, M, s, b outside brackets and quotes), so also with upper-case formats such as `DD.MM.YYYY`, in `xlsx.load` and, except for formula results, in the streaming reader, as exceljs's `cell-xform.js` L449-459 and `worksheet-reader.js` L329-334 do. Dates from 1900-01-01 to 1900-02-28 and dates outside 1900-9999 differ, see F6 and F8 under [Known differences from the 1.3.0 review](#known-differences-from-the-130-review). | `lib/doc/cell.js` L538; `lib/xlsx/xform/style/styles-xform.js` L253–266, L498–515 |
108
108
  | `{ formula, result }` | supported | xlvane does not calculate; `result` is stored as given. | `lib/doc/cell.js` L734 |
109
- | `{ sharedFormula, result }`, `{ formula, shareType, ref, result }`, `formulaType` | supported | Shared formulas are written as `t="shared"` groups: the master's `ref` is its own `ref` or grows to its last dependent, as in exceljs; array formulas (`shareType: 'array'`) are written with their range (X4-29). Read back as exceljs reads them: the master `{ formula, shareType: 'shared', ref, result }`, a dependent `{ sharedFormula, result }` whose `cell.formula` is the translated formula, moved from the master when `cell.formula` is read (loading does not build the dependents' texts, which a huge master shared with many cells made exhaust memory; K2). The moved text is kept on the dependent cell until its value, its master's formula or the position of either changes (exceljs keeps it per value, `lib/doc/cell.js` L861-869, and does not see a later change of the master's formula). The shared-formula text moved in one workbook (each master text read plus each moved text built) may not pass 32 Mi characters plus 256 for each move, so formulas of up to 128 characters never reach the limit; a move past it throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `sharedFormulaText`, with the dependent's location) from `cell.formula`, because each move costs time in proportion to the master's length: a 4 KB file sharing a 1 000 000-character master with 250 cells took about 47 s to read every dependent's formula once (K4 S1). exceljs has no such limit. A dependent the master's formula cannot be moved to (outside the master's range, or a formula xlvane cannot move safely) is read as its cached value (exceljs reads `{ sharedFormula, result }`). `formulaType` is 0 (none), 1 (master) or 2 (shared). The streaming `WorkbookWriter` writes a dependent of a master with `ref` as a shared formula, and any other `{ sharedFormula }` cell as its translated formula, which needs the master's row to be uncommitted, or a dependent from the streaming `WorkbookReader`: it is `{ sharedFormula, result }` as above, with its translated formula as a non-enumerable `formula` built when it is read (not in `Object.keys` or JSON), so copying its rows with `addRow(row.values).commit()` writes each dependent as its formula (K3). Differences: xlvane refuses (`XLV_E_INVALID_VALUE`) what exceljs writes against the schema rules of Part 1 §18.3.1.40: overlapping shared ranges, a dependent outside its master's range, and a formula in another cell of an array formula's range. A splice (`spliceRows`, `spliceColumns`, `insertRow(s)`, `duplicateRow` with insertion) turns every shared group of the workbook into plain formulas, each adjusted for the splice, because moving the master's text to each dependent would give wrong references; array formulas keep their group and their range moves (D-E06). A `$` in a shared master's `ref` (exceljs `fillFormula` over an absolute range) is read as the same range without `$`. | `lib/doc/cell.js` L744, L811; `lib/xlsx/xform/sheet/cell-xform.js` L94–160, L460–470 |
109
+ | `{ sharedFormula, result }`, `{ formula, shareType, ref, result }`, `formulaType` | supported | Shared formulas are written as `t="shared"` groups: the master's `ref` is its own `ref` or grows to its last dependent, as in exceljs; array formulas (`shareType: 'array'`) are written with their range (X4-29). Read back as exceljs reads them: the master `{ formula, shareType: 'shared', ref, result }`, a dependent `{ sharedFormula, result }` whose `cell.formula` is the translated formula, moved from the master when `cell.formula` is read (loading does not build the dependents' texts, which a huge master shared with many cells made exhaust memory; K2). The moved text is kept on the dependent cell until its value, its master's formula or the position of either changes (exceljs keeps it per value, `lib/doc/cell.js` L861-869, and does not see a later change of the master's formula). The shared-formula text moved in one workbook (each master text read plus each moved text built) may not pass 32 Mi characters plus 256 for each move, so formulas of up to 128 characters never reach the limit; a move past it throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `sharedFormulaText`, with the dependent's location) from `cell.formula`, because each move costs time in proportion to the master's length: a 4 KB file sharing a 1 000 000-character master with 250 cells took about 47 s to read every dependent's formula once (K4 S1). exceljs has no such limit. A dependent the master's formula cannot be moved to (outside the master's range, or a formula xlvane cannot move safely) is read as its cached value (exceljs reads `{ sharedFormula, result }`). `formulaType` is 1 (master) or 2 (shared) for a formula, and `undefined` for a cell without one and for the other cells of a merged range, as in exceljs (`lib/doc/cell.js` L264-266, L807-815; exceljs's declaration says `FormulaType`). The streaming `WorkbookWriter` writes a dependent of a master with `ref` as a shared formula, and any other `{ sharedFormula }` cell as its translated formula, which needs the master's row to be uncommitted, or a dependent from the streaming `WorkbookReader`: it is `{ sharedFormula, result }` as above, with its translated formula as a non-enumerable `formula` built when it is read (not in `Object.keys` or JSON), so copying its rows with `addRow(row.values).commit()` writes each dependent as its formula (K3). Differences: xlvane refuses (`XLV_E_INVALID_VALUE`) what exceljs writes against the schema rules of Part 1 §18.3.1.40: overlapping shared ranges, a dependent outside its master's range, and a formula in another cell of an array formula's range. A splice (`spliceRows`, `spliceColumns`, `insertRow(s)`, `duplicateRow` with insertion) turns every shared group of the workbook into plain formulas, each adjusted for the splice, because moving the master's text to each dependent would give wrong references; array formulas keep their group and their range moves (D-E06). A `$` in a shared master's `ref` (exceljs `fillFormula` over an absolute range) is read as the same range without `$`. | `lib/doc/cell.js` L744, L811; `lib/xlsx/xform/sheet/cell-xform.js` L94–160, L460–470 |
110
110
  | A style on a cell without a value | supported | Written and read back: xlvane's reader keeps cells that hold only a style (D-E05). | `lib/xlsx/xform/sheet/cell-xform.js` |
111
111
  | `{ error: '#N/A' }` | supported | | `lib/doc/cell.js` L970 |
112
112
  | Other objects and arrays (JSON values) | supported | Kept on the cell with type `String` and written as their JSON text, as exceljs; read back as that text. | `lib/doc/cell.js` L1014–1054 |
@@ -124,12 +124,12 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
124
124
  | API | Status | Notes | Source in exceljs |
125
125
  |---|---|---|---|
126
126
  | `numFmt` | supported | Codes that exceljs maps to built-in formats (`'0.00'`, `'mm-dd-yy'`, `'@'`, …) are written as the built-in id, as exceljs does. | `lib/doc/cell.js` L51; `lib/xlsx/defaultnumformats.js`; `lib/xlsx/xform/style/styles-xform.js` L374 |
127
- | `font` | supported | All exceljs font properties are written and read back (`vertAlign`, `family`, `scheme`, `charset`, `outline`, `shadow`, `condense`, `extend` too; X4-27). A font without `name` or `size` is written with the workbook's default font for the missing ones (Calibri 11), so it reads back with them; exceljs writes only what is given. A single underline (`underline: true` or `'single'`) is written as `<u/>` and reads back as `true`, as in exceljs (`underline-xform.js` L14–30, L40–45); a file with `<u val="single"/>`, which exceljs reads as `'single'`, also reads back as `true` here. | `lib/doc/cell.js` L59; `lib/xlsx/xform/style/font-xform.js`; `index.d.ts` L217 |
127
+ | `font` | supported | All exceljs font properties are written and read back (`vertAlign`, `family`, `scheme`, `charset`, `outline`, `shadow`, `condense`, `extend` too; X4-27). A font without `name` or `size` is written with the workbook's default font for the missing ones (Calibri 11), so it reads back with them; exceljs writes only what is given. The workbook's default font (font 0, the font of cells without a style) of a file read with `xlsx.load` is written again as font 0, for example Arial 10, so its cells keep it; parts of it the writer does not accept (a name of more than 31 characters or an empty one, a size outside 1 to 409.55 points) are left out, so such a file is still written; a workbook made in code, and the streaming writer, write Calibri 11. exceljs always writes Calibri 11 as font 0, so after its load and write cells without a style show Calibri 11. As in exceljs, a style read from a file (a cell with `s` of 1 or more, a row or column style) has the font its `fontId` names also when that is font 0 (`lib/xlsx/xform/style/styles-xform.js` L296-345), so `cell.font` is, for example, `{ name: 'Arial', size: 10 }`; a cell with `s="0"` or no `s` has none (`lib/xlsx/xform/sheet/cell-xform.js` L428-432). As in exceljs, a column or row with style 0 (`<col style="0">`, which LibreOffice writes on every column, or `<row s="0" customFormat="1">`) has no style (`col-xform.js` L80, `row-xform.js` L130); xlvane's core gives no style index for columns and rows, so a column or row style equal to xf 0 (the style of a cell without `s`; when no worksheet has such a cell, xf 0 is not known and style 0 reads like other styles) counts as style 0, and also a row or column style with an `s` of 1 or more whose cell format equals xf 0, or that has nothing but font 0, reads without a font. The streaming reader does not add font 0 (the core's streaming reader does not give the default font). A single underline (`underline: true` or `'single'`) is written as `<u/>` and reads back as `true`, as in exceljs (`underline-xform.js` L14–30, L40–45); a file with `<u val="single"/>`, which exceljs reads as `'single'`, also reads back as `true` here. | `lib/doc/cell.js` L59; `lib/xlsx/xform/style/font-xform.js`; `index.d.ts` L217 |
128
128
  | `fill` (pattern and gradient) | supported | Gradient fills: `gradient: 'angle'` with `degree` is written as a linear gradient, `gradient: 'path'` with `center` (`right`/`bottom` default to `left`/`top`, as exceljs) and the stops; read back in exceljs's shape (X4-27). | `lib/doc/cell.js` L83; `lib/xlsx/xform/style/fill-xform.js`; `index.d.ts` L161–215 |
129
129
  | `border` (`top`, `left`, `bottom`, `right`, `diagonal`) | supported | `diagonal` with `up`, `down`, `style` and `color`, as in exceljs (X4-27). | `lib/doc/cell.js` L75; `lib/xlsx/xform/style/border-xform.js`; `index.d.ts` L258 |
130
130
  | `alignment` | supported | `horizontal`, `vertical`, `wrapText`, `shrinkToFit`, `indent`, `readingOrder` (`'ltr'`/`'rtl'`) and `textRotation` (`'vertical'` is 255, −90…−1 are 91…180, as exceljs writes them; X4-27). `textRotation` and `indent` follow exceljs's `validInt` (D-E06): numbers are truncated and numeric strings accepted; a rotation outside −90…90 (other than `'vertical'`) is dropped and an indent below 0 becomes 0, as in exceljs. An indent above 255 in a loaded file (more than Office accepts) is left out, so the workbook can be written again. | `lib/doc/cell.js` L67; `lib/xlsx/xform/style/alignment-xform.js`; `index.d.ts` L280 |
131
131
  | `protection` (`locked`, `hidden`) of cells, rows, columns and styles | supported | Written when it differs from the defaults (`locked: true`, `hidden: false`), as in exceljs (X4-27). | `lib/xlsx/xform/style/protection-xform.js` |
132
- | Colours `{ argb }`, `{ theme, tint }`, `{ indexed }` | partly | A six-digit `rgb` read from a file is dropped (F5 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)). | `index.d.ts` |
132
+ | Colours `{ argb }`, `{ theme, tint }`, `{ indexed }` | partly | A six-digit `rgb` read from a file (`<color rgb="AA0000"/>`) reads as the opaque colour `{ argb: 'FFAA0000' }` and is written back with these 8 digits; exceljs reads `{ argb: 'AA0000' }` and writes the 6 digits back (`lib/xlsx/xform/style/color-xform.js` L19-20, L39-40). A six-digit `argb` set in code is likewise written as `FF` followed by the six digits; exceljs writes the six digits. | `index.d.ts`; `lib/xlsx/xform/style/color-xform.js` |
133
133
 
134
134
  ## Streaming
135
135
 
@@ -188,21 +188,9 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
188
188
 
189
189
  - None so far for what the sample reads back. The sample covers document properties (including `company` and `manager`), rich text, a column style, defined names (including `cell.name`), the built-in date format, a tab colour, a zoomed frozen view external, `mailto:` and `#` links (without tooltips, which exceljs does not read back), gradient fills, a diagonal border, alignment (`textRotation`, `shrinkToFit`, `readingOrder`) and protection, a shared formula from `fillFormula`, outline levels of rows and columns, workbook views, and `insertRow` with style `'i'`, `spliceRows`, `duplicateRow`, `spliceColumns`, `insertRows` and `row.splice` on plain values (no formulas, merges, notes or names, which exceljs does not move); CSV written with the default and with custom options and read back (no formula-like text, no formula result 0, no gap before the first row); `cross-check.test.ts` and `alias.test.ts` also run the documented deep imports (`deep-imports.cjs`); `alias.test.ts` also checks that LibreOffice shows the dates as exceljs's files do (`1/31/2024`).
190
190
 
191
- ## Known differences planned for 1.3.1
191
+ ## Known differences from the 1.3.0 review
192
192
 
193
- Found by a review of 1.3.0 and checked by running exceljs 4.4.0 and `@xlvane/compat` on the same input (Node.js 22.23). They are not fixed yet.
193
+ Found by a review of 1.3.0 and checked by running exceljs 4.4.0 and `@xlvane/compat` on the same input (Node.js 22.23). F6 describes a difference that xlvane keeps, and F8 values that xlvane refuses. F1, F2, F3, F4, F5, F7, F9, F10 and F12 of that review (merged-cell styles, row styles, the default font, six-digit colours, upper-case date formats, CSV formatter options, `latin1` CSV reading, `column.eachCell`) are fixed in 1.3.1 and described in the tables above, with the differences that remain.
194
194
 
195
- - **F1** A style set on the top-left cell after `mergeCells` (for example `ws.getCell('A1').fill = ...`) does not reach the other cells of the range in memory: `B2.fill` stays `undefined`. exceljs gives every merged cell the master's style object (`lib/doc/cell.js` L168-174, `this.style = master.style`), so a later change of the master's `fill` or `font` shows on all of them. xlvane copies the master's style when merging (`packages/compat/src/model.ts`, `mergeCells`). The written file is the same: the writer gives merged cells the master's style.
196
- - **F2** `xlsx.load` gives every cell of a merged range the top-left cell's style, replacing the style each cell has in the file (for example a right border on the last cell). exceljs merges loaded ranges without styles (`lib/doc/worksheet.js` L895-901 `_parseMergeCells` calls `mergeCellsWithoutStyle`), so each cell keeps its own. xlvane calls `mergeCells` there (`packages/compat/src/workbook.ts`, `_fill`).
197
- - **F3** Row styles (`row.fill`, `row.font` and the other row style setters) apply to the row's existing cells only. exceljs also writes the row's own style as `<row s="..." customFormat="1">` (`lib/xlsx/xform/sheet/row-xform.js` L21-23, L44-46) and reads it back as `row.style` (L81, L130), from which cells created later in the row take their style. xlvane neither writes nor reads it.
198
- - **F4** A cell whose style uses the workbook's default font (font 0, for example Arial 10, with a number format of its own) reads with `font` `undefined`, and after `xlsx.load` and `writeBuffer` it shows Calibri 11. exceljs reads font 0 into `cell.font` (`lib/xlsx/xform/style/styles-xform.js` L318-327, `addStyle` also for id 0) and writes it back as a font of its own. Cells without a style (`s="0"`) are written with Calibri 11 by both.
199
- - **F5** A six-digit `rgb` colour in a file's styles (`<color rgb="AA0000"/>`) is dropped on reading: the font reads as `{}`. exceljs reads it as `{ argb: 'AA0000' }` (`lib/xlsx/xform/style/color-xform.js` L39-40) and writes it back unchanged (L19-20). When a six-digit `argb` is set in code, xlvane writes `FF` followed by the six digits; exceljs writes the six digits.
200
- - **F6** Dates near the start of the 1900 date system:
201
- - A time-only date such as `new Date(Date.UTC(1899, 11, 30, 13, 20))` throws `XLV_E_INVALID_VALUE` when written ("outside the 1900 date system"); exceljs writes serial 0.5555... (`lib/utils/utils.js` L55-57 `dateToExcel`).
202
- - A time-only serial (below 1) with a date format reads as 1899-12-31 plus the time; exceljs gives 1899-12-30 plus the time (`lib/utils/utils.js` L58-61 `excelToDate`, which counts from 1899-12-30).
203
- - Dates from 1900-01-01 to 1900-02-28 differ by one day. xlvane writes and reads them with serial 1 = 1900-01-01 (ECMA-376 Part 4 §15.3.4, docs/facts/ecma-376.md E-15); exceljs writes 1900-01-15 as serial 16 and reads serial 15 as 1900-01-14 (same functions), so here exceljs is the one that is off. From 1900-03-01 on both agree.
204
- - **F7** A number whose format uses upper-case date letters (`DD.MM.YYYY`, `YYYY-MM-DD`, `HH:MM`) reads as a number. exceljs reads it as a `Date`: its test is `/[ymdhMsb]+/` after removing quoted and bracketed parts (`lib/utils/utils.js` L140-151 `isDateFmt`), which the upper-case `M` matches.
195
+ - **F6** Dates from 1900-01-01 to 1900-02-28 differ by one day. xlvane writes and reads them with serial 1 = 1900-01-01 (ECMA-376 Part 4 §15.3.4, docs/facts/ecma-376.md E-15); exceljs writes 1900-01-15 as serial 16 and reads serial 15 as 1900-01-14 (`lib/utils/utils.js` L55-61), so here exceljs is the one that is off, and xlvane keeps its reading. From 1900-03-01 on both agree. Serial 60 (the 29 February 1900 that does not exist) and negative serials with a date format read as numbers (exceljs gives 1900-02-28 and dates before 1899-12-30). In a workbook with the 1904 date system a `Date` before 1904-01-01, such as a time-only value given as 1899-12-30 plus the time, throws `XLV_E_INVALID_VALUE` when written; exceljs writes a negative serial (-1461.44... for 1899-12-30T13:20). Time-only values in the 1900 date system and upper-case date formats such as `DD.MM.YYYY` (F6 and F7 of the 1.3.0 review) behave as in exceljs from 1.3.1.
205
196
  - **F8** Values exceljs writes without checks (`lib/xlsx/xform/sheet/cell-xform.js` L199-250, `render`) that xlvane refuses with `XLV_E_INVALID_VALUE` when the workbook is written: `NaN` and `Infinity` (exceljs writes `<v>NaN</v>`, `<v>Infinity</v>`), an invalid `Date` (exceljs writes `<v>NaN</v>`), dates before 1900-01-01 or after 9999-12-31 (exceljs writes the serial, for example -18260 for 1850-01-01), text of more than 32 767 characters, text with an unpaired UTF-16 surrogate, and formulas of more than 8 192 characters.
206
- - **F9** `csv.write` and `csv.writeBuffer` ignore, without an error, three fast-csv 4.3 formatter options that exceljs passes on (`lib/csv/csv.js` L118 `fastCsv.format(options.formatterOptions)`): `transform` (`@fast-csv/format/build/src/formatter/RowFormatter.js` L22-23), `quoteHeaders`, and `quoteColumns` given as an object of header names (`@fast-csv/format/build/src/formatter/FieldFormatter.js` L24-35 `shouldQuote`). `quoteColumns` as `true` or as an array works.
207
- - **F10** `csv.read` with `parserOptions.encoding: 'latin1'` decodes with the WHATWG `TextDecoder('latin1')`, which is windows-1252: the bytes 80 and 93 become U+20AC and U+201C. exceljs, through fast-csv's Node.js `StringDecoder` (`@fast-csv/parse/build/src/CsvParserStream.js` L20), gives U+0080 and U+0093.
208
- - **F12** `column.eachCell(cb)` without `includeEmpty` visits only the column's cells that have values. exceljs visits the column's cell in every row that has a value in any column (`lib/doc/column.js` L168-177, through `worksheet.eachRow`), so an empty cell next to a filled one is visited too (and created).
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # xlvane 1.3.0: documentation in one file
1
+ # xlvane 1.3.2: documentation in one file
2
2
 
3
3
  This file joins the README of the xlvane package, the Agent Skill and the documentation in docs/. It is generated by scripts/package-docs.mjs; the separate files are in the same package.
4
4
 
@@ -110,7 +110,8 @@ In Node.js, `writeWorkbookFile(workbook, 'sales.xlsx')` from `xlvane/node` write
110
110
 
111
111
  - Values: numbers, strings, booleans, `Date`s (stored as serial numbers with a `yyyy-mm-dd` or `yyyy-mm-dd hh:mm:ss` format unless the style sets one), `{ error: '#N/A' }`, and `{ formula, result }`, also as an array formula (`{ formula, array: 'A1:A3' }`) or a shared formula (`{ formula, shared: 'B1:B9' }` on the first cell, `{ sharedFormula: 'B1' }` on the others). xlvane writes the formula text and the cached result you give; it does not calculate formulas.
112
112
  - Styles: font (name, size, bold, italic, underline, strike, colour, superscript and subscript, family, scheme, charset, outline, shadow), fill (solid colour, pattern, or linear and path gradient), borders (top, right, bottom, left, diagonal), alignment (horizontal, vertical, wrap, shrink to fit, indent, rotation, reading order), cell protection (`locked`, `hidden`) and number format. Identical styles are stored once. `sheet.style(range, style)` merges a style into every cell of a range.
113
- - Layout: column widths and hidden columns, row heights and hidden rows, outline levels of rows and columns (`row(n, { outlineLevel: 1, collapsed })`, `column('B', { outlineLevel: 1 })`), merged ranges, frozen rows and columns, autofilter, hidden and very hidden sheets. `sheet.properties({ defaultRowHeight, outlineLevelRow, outlineLevelCol, summaryBelow, summaryRight })` sets the sheet's format properties.
113
+ - Layout: column widths and hidden columns, row heights and hidden rows, outline levels of rows and columns (`row(n, { outlineLevel: 1, collapsed })`, `column('B', { outlineLevel: 1 })`), the default style of a row (`row(n, { style })`, read back as `sheet.rowInfo(n).style`; it is not applied to the row's cells), merged ranges, frozen rows and columns, autofilter, hidden and very hidden sheets. `sheet.properties({ defaultRowHeight, outlineLevelRow, outlineLevelCol, summaryBelow, summaryRight })` sets the sheet's format properties.
114
+ - Default font: `createWorkbook({ defaultFont: { name: 'Arial', size: 10 } })` sets the workbook's default font (Calibri 11 otherwise); a read workbook gives it as `workbook.defaultFont`.
114
115
  - Workbook: `workbook.views([{ x, y, width, height, firstSheet, activeTab, visibility }])` sets the workbook windows (`activeTab` and `firstSheet` are checked against the sheets when the workbook is written) and `workbook.calculation({ fullCalcOnLoad: true })` asks applications to recalculate when the file is opened. `addSheet(name, { sheetId })` sets the sheet's id in the file (1 to 65534, unique; other sheets get the lowest unused ids), and reading gives `sheet.sheetId`.
115
116
  - Writer options: `writeWorkbook(workbook, { sharedStrings: false })` writes text inline instead of in a shared string table; `{ compressionLevel: 0 }` (0–9, default 6) stores or compresses the parts. The same options work for `writeWorkbookFile` and, with `compressionLevel`, `writeWorkbookStream`.
116
117
  - Invalid input is rejected when it is set, with an `XlvError` saying what and where, for example `XLV_E_INVALID_SHEET_NAME` or `XLV_E_INVALID_STYLE` with the allowed values.
@@ -794,7 +795,7 @@ From 1.3.0 (D-K04):
794
795
  - `maxCells`: `insertRows` with `style: 'above'` or `'below'` (and `xlvane edit --insert-rows` with a style) counts the formatted cells copied into the new rows (the formatted cells of the copied row times the number of rows) plus the cells of the workbook against `maxCells`, before the rows are built. Insert fewer rows, use `style: 'none'`, or raise `maxCells`.
795
796
  - `sharedFormulaText`: `@xlvane/compat` `cell.formula` of a `{ sharedFormula }` dependent moves the master's formula once and keeps the result; the text moved per workbook (master text read plus moved text built) is limited to 512 Mi characters, the default `maxSharedStringChars` with which `readWorkbook` expands shared formulas, and for masters longer than 8192 characters (the longest formula Office writes, [MS-OI29500] §2.1.1085 a) to 32 Mi characters; also in `stream.xlsx.WorkbookReader` (per read). `location` names the dependent cell; its cached `cell.result` stays readable.
796
797
 
797
- From 1.3.0 (D-K03): `@xlvane/compat` `xlsx.load` limits merged cells to `max(1 048 576, cells in the file)` (`details.limit` `mergedCells`; each merged cell is written with the master's style, so a few bytes of `mergeCell` could ask for millions of styled cells), and `csv.read` stops at more than 1 048 576 rows (`details.limit` `maxRows`) 16 384 fields in a row (`details.limit` `maxColumns`, with `location.line`) or 5 000 000 fields in all (`details.limit` `maxCells`) while it parses.
798
+ From 1.3.0 (D-K03): `@xlvane/compat` `xlsx.load` limits merged cells to `max(1 048 576, cells in the file)` (`details.limit` `mergedCells`; each merged position becomes a cell, which a row or column style gives a style, so a few bytes of `mergeCell` could ask for millions of styled cells), and `csv.read` stops at more than 1 048 576 rows (`details.limit` `maxRows`) 16 384 fields in a row (`details.limit` `maxColumns`, with `location.line`) or 5 000 000 fields in all (`details.limit` `maxCells`) while it parses.
798
799
 
799
800
  The MCP server (`@xlvane/mcp`) returns it as a tool error for a file larger than `--max-file-size` (`details.limit` `maxFileBytes`; `convert` to JSON stops building the text at that size), for a workbook with more cells than `--max-cells` (`details.limit` `maxCells`) and for a result longer than `--max-response-chars` (`details.limit` `maxResponseChars`; `read_table` instead returns the rows that fit). `xlvane convert` to JSON returns it when the text would be longer than a JavaScript string can be.
800
801
 
@@ -1032,7 +1033,7 @@ A sheet defines the same cell more than once. The last definition is used, also
1032
1033
  **Fix:** nothing, if the last definition is right; otherwise re-save the file in the application that made it.
1033
1034
 
1034
1035
  ### XLV_W_INVALID_VALUE
1035
- A value does not match its declared type and is returned in a safe form, for example a numeric cell whose text is not a number (returned as text), a boolean cell that is neither 0 nor 1, a shared-string index that does not exist (the cell is treated as empty), a date serial outside the date system (returned as a number), an unknown sheet state (treated as visible), or an invalid style index or range. The message says what was found and what was done. A chart is skipped with this warning when its part is not well-formed XML, is not encoded in UTF-8 or UTF-16, has no chart with a plot area, or when the chart's relationship leads to a part whose content type is not the chart content type (D-K01).
1036
+ A value does not match its declared type and is returned in a safe form, for example a numeric cell whose text is not a number (returned as text), a boolean cell that is neither 0 nor 1, a shared-string index that does not exist (the cell is treated as empty), a date serial outside the date system (returned as a number), an unknown sheet state (treated as visible), a colour `rgb` of 6 hex digits instead of the 8 of an ARGB value (read as an opaque RGB colour, `FF` followed by the 6 digits; docs/facts/sml-write.md W2-16a), or an invalid style index or range. The message says what was found and what was done. A chart is skipped with this warning when its part is not well-formed XML, is not encoded in UTF-8 or UTF-16, has no chart with a plot area, or when the chart's relationship leads to a part whose content type is not the chart content type (D-K01).
1036
1037
  **Fix:** read the message for the cell and the value used; re-save the file in the application that made it to repair the data.
1037
1038
 
1038
1039
  ### XLV_W_UNUSUAL_HYPERLINK
@@ -1320,7 +1321,7 @@ npm installs an alias `<alias>@npm:<name>` under the alias name in `node_modules
1320
1321
 
1321
1322
  - **ESM:** `import ExcelJS from 'exceljs'` works as with exceljs. `@xlvane/compat` is an ES module, so named imports (`import { Workbook } from 'exceljs'`) work too; exceljs 4.4.0 rejects them because it is a CommonJS package.
1322
1323
  - **CommonJS:** `const ExcelJS = require('exceljs')` needs **Node.js 22.12 or later**. The package is ESM-only and exports the `ExcelJS` object under the name `"module.exports"`, which `require()` of an ES module returns directly from Node.js 22.12 on (Node.js `doc/api/modules.md`, "Loading ECMAScript modules using `require()`"). `engines.node` is `>=22.12`.
1323
- - **Unsupported API** throws an `XlvError` with code `XLV_E_NOT_SUPPORTED`, naming the method (docs/errors.md). A few options and settings are still ignored without an error; they are listed under [Known differences planned for 1.3.1](#known-differences-planned-for-131).
1324
+ - **Unsupported API** throws an `XlvError` with code `XLV_E_NOT_SUPPORTED`, naming the method (docs/errors.md). Options and settings that are accepted but not handled as exceljs handles them are described with the API they belong to (for example `formatterOptions.objectMode` of `csv.write` has no effect); the [Known differences from the 1.3.0 review](#known-differences-from-the-130-review) lists further differences in values.
1324
1325
 
1325
1326
  Status: **supported** means the same behaviour as exceljs for the cases covered by the ported tests (`tests/exceljs/`). **Partly** means supported with the difference given in the notes. **Not supported** means it throws `XLV_E_NOT_SUPPORTED`. The source column points to the exceljs 4.4.0 implementation (paths relative to the package root, as installed in `bench/node_modules/exceljs`).
1326
1327
 
@@ -1338,15 +1339,15 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1338
1339
  | `views` (workbook windows) | supported | Every view is written as a `workbookView` with exceljs's defaults (`x` 0, `y` 0, `width` 12000, `height` 24000) and `firstSheet`, `activeTab`, `visibility` when given; read back with them (X4-28). | `lib/doc/workbook.js` L28, L164; `lib/xlsx/xform/book/workbook-view-xform.js` |
1339
1340
  | `calcProperties` (`fullCalcOnLoad`) | partly | `fullCalcOnLoad: true` is written as `calcPr fullCalcOnLoad="1"` and read back. exceljs always writes `calcPr calcId="171027"`; xlvane writes `calcPr` only when `fullCalcOnLoad` is set and never a `calcId` (sml.xsd CT_CalcPr makes it optional; xlvane does not calculate, so it claims no calculation engine version; the meaning of `calcId` is not checked against Part 1 prose, PROVISIONAL). `calcProperties` reads back as `{ fullCalcOnLoad: true }` or `{}`; exceljs reads `{}` in both cases. | `lib/doc/workbook.js` L24, L177; `lib/xlsx/xform/book/workbook-calc-properties-xform.js` |
1340
1341
  | `model` (get / set), `nextId`, `clearThemes()` | supported | `model` is a plain snapshot of the workbook that can be set on another workbook (it holds xlvane's worksheet models, not exceljs's internal xform models). `clearThemes()` does nothing: xlvane never keeps or writes a theme part. | `lib/doc/workbook.js` L43, L137, L153–215 |
1341
- | `xlsx.load(buffer, options)` | supported | Reading goes through xlvane's limits for untrusted files. Options as in exceljs (X4-25): `base64` reads base64 text; `ignoreNodes` skips worksheet elements by name (`sheetPr`, `cols`, `sheetData`, `sheetViews`, `autoFilter`, `mergeCells`, `hyperlinks`, `dataValidations`, `conditionalFormatting`, `sheetProtection`, `pageSetup`, `headerFooter`, `rowBreaks`, `drawing`, `picture`, `tableParts`); `maxRows` and `maxCols` refuse bigger worksheets with `XLV_E_LIMIT_EXCEEDED` (exceljs throws a plain `Error`). The same options apply to `readFile` and `read`. Merged ranges may cover at most max(1 048 576, cells in the file) positions in total (one whole column fits); more throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `mergedCells`), because each merged cell carries the master's style and is written as a styled cell, so the cost of `writeBuffer` grows with the merged positions: a merged range whose cells all carry the master's style is styled in blocks of rows (K2), also when some of its cells have notes (K3), and even so 1 048 575 merged positions took about 3.5 s and 750 MB (peak resident memory) to write (measured with Node.js 22.23, K3). `ignoreNodes: ["mergeCells"]` skips them. | `lib/xlsx/xlsx.js` L257; `lib/xlsx/xform/sheet/worksheet-xform.js` L95, L372 |
1342
+ | `xlsx.load(buffer, options)` | supported | Reading goes through xlvane's limits for untrusted files. Options as in exceljs (X4-25): `base64` reads base64 text; `ignoreNodes` skips worksheet elements by name (`sheetPr`, `cols`, `sheetData`, `sheetViews`, `autoFilter`, `mergeCells`, `hyperlinks`, `dataValidations`, `conditionalFormatting`, `sheetProtection`, `pageSetup`, `headerFooter`, `rowBreaks`, `drawing`, `picture`, `tableParts`); `maxRows` and `maxCols` refuse bigger worksheets with `XLV_E_LIMIT_EXCEEDED` (exceljs throws a plain `Error`). The same options apply to `readFile` and `read`. Merged ranges may cover at most max(1 048 576, cells in the file) positions in total (one whole column fits); more throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `mergedCells`), because each merged position becomes a cell, which takes the row and column style, and a cell with a style is written as a styled cell, so the cost of `writeBuffer` grows with the merged positions: the rows of a merged range whose cells all carry the master's style (or an equal one, for example from a row or column style) are styled in blocks (K2), also when some of its cells have notes (K3), and even so 1 048 575 merged positions took about 3.5 s and 750 MB (peak resident memory) to write (measured with Node.js 22.23, K3). `ignoreNodes: ["mergeCells"]` skips them. As in exceljs, the ranges are merged without styles (`mergeCellsWithoutStyle`), so each cell of a merged range keeps its own style from the file, and a cell the file leaves out gets the row and column style. | `lib/xlsx/xlsx.js` L257; `lib/xlsx/xform/sheet/worksheet-xform.js` L95, L372; `lib/doc/worksheet.js` L895-901 |
1342
1343
  | `xlsx.readFile(path)` | supported | | `lib/xlsx/xlsx.js` L49 |
1343
1344
  | `xlsx.read(stream)` | supported | | `lib/xlsx/xlsx.js` L244 |
1344
1345
  | `xlsx.writeBuffer(options)` | supported | `useStyles: false` writes no cell or column styles, dates keeping format 14, as exceljs. `useSharedStrings: false` writes inline strings and no shared string table; `zip.compression: 'STORE'` stores every part, `zip.compressionOptions.level` (0–9) sets the deflate level; other `zip` options are ignored (X4-25). The output stays deterministic. The same options apply to `writeFile` and `write`. | `lib/xlsx/xlsx.js` L602–603, L685 |
1345
1346
  | `xlsx.writeFile(path)` | supported | | `lib/xlsx/xlsx.js` L666 |
1346
1347
  | `xlsx.write(stream)` | supported | | `lib/xlsx/xlsx.js` L643 |
1347
1348
  | `xlsx.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/xlsx/xlsx.js` L238 |
1348
- | `csv.read(stream, options)`, `csv.readFile(path, options)` | supported | Adds a worksheet, with exceljs's options (`sheetName`, `dateFormats`, `map`, `parserOptions`: `delimiter`, `quote`, `escape`, `trim`/`ltrim`/`rtrim`, `ignoreEmpty`, `comment`, `skipLines`, `skipRows`, `maxRows`, `headers`, `renameHeaders`, `encoding`; `latin1` is decoded as windows-1252, F10 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)) and its default conversion of fields (X4-23), on xlvane's own parser (no fast-csv). Accepts a Node.js readable stream, a Web `ReadableStream` or any (async) iterable of strings or bytes. Differences: text of white space only stays text (exceljs reads `' '` as 0), `-Infinity` stays text (it cannot be stored), a leading byte order mark is dropped, as in exceljs (fast-csv 4.3.6 removes it: `@fast-csv/parse/build/src/parser/Parser.js` L12-19 `removeBOM`, called by `parse` at L20-23), `headers: true` skips the header line (exceljs throws `data.map is not a function`), a missing file or a malformed line throws `XLV_E_FILE_READ` / `XLV_E_CSV_MALFORMED`. `readFile` needs `node:fs` (Node.js, Bun, Deno). Dates are parsed as dayjs 1.11 customParseFormat parses them in strict mode (D-E06): letters that are no token, such as an unbracketed `T` or `dddd`, are skipped, and each token is searched in the rest of the text. An unclosed quote is reported on the line where it opens; a `quote` or `escape` longer than one character throws `XLV_E_INVALID_OPTION`. Rows are added as they are parsed, as fast-csv streams them, so a malformed line leaves the rows before it in the worksheet. More than 1 048 576 rows, or a row of more than 16 384 fields, throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxRows` or `maxColumns`) as soon as parsing reaches it (exceljs collects the rows and fails with a plain `Error` in `addRow`); rows left out by `ignoreEmpty`, the header, `skipRows` and `maxRows` do not count (K2). More than 5 000 000 fields in the rows added (`DEFAULT_READ_LIMITS.maxCells`, the core's cell limit) throw `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxCells`, with the line) before any row is added: a text of 5 000 000 characters or more is counted in a first pass (K3; exceljs has no such limit, and 10 MB of `1,1,…` took 885 MB). | `lib/csv/csv.js` L32–98 |
1349
- | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`) and fast-csv's quoting (X4-24); `transform`, `quoteHeaders` and `quoteColumns` given as an object are ignored (F9 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)); the cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10, CSV-12; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`; with `latin1` characters above U+00FF are written as `?` (Node.js, and so exceljs, keeps only their low byte, which can turn U+013D into `=`), and formatter options with such characters throw `XLV_E_INVALID_OPTION`. While formulas are escaped (the default), a `delimiter` holding a formula sign (`=`, `+`, `-`, `@`, their full-width or small forms) or a control character (a lone tab is allowed), a `rowDelimiter` other than `\n`, `\r\n` or `\r`, a `quote` other than `"` or `false`, an `escape` other than the quote, and with `quote: false` a field that starts with `"` throw `XLV_E_INVALID_OPTION`, because a standard CSV reader would otherwise get a field starting with a formula (docs/facts/csv.md CSV-13); `formulaInjection: "off"` writes them as exceljs does. Also while formulas are escaped (K2): text that starts with a byte order mark (U+FEFF or U+FFFE) is escaped, because applications drop a BOM at the start of a file and run what follows; with `encoding: "latin1"` or `"utf16le"` a field is also escaped when its first bytes as written are a BOM (EF BB BF, FF FE, FE FF, for example the latin1 text `=1+1`) or the UTF-8 form of a full-width or small formula sign, and with `latin1` and `writeBOM` (written as the byte FF) a first field starting with `þ`; a value from `map` that is not a number, boolean, `null` or `undefined` (a `String` object, an array, an object with `toString`) is turned into its text once and checked like text (fast-csv writes its text unchecked); a field holding a comma, `;`, tab or `|` is quoted whatever the delimiter (fast-csv quotes only the delimiter and `|`), because an application that splits on another of these characters would see a new field there, and with `quote: false` a field where one of them is followed by a formula sign (after spaces and an opening `"`) throws `XLV_E_INVALID_OPTION`. Also while formulas are escaped (K3): an empty `delimiter` throws `XLV_E_INVALID_OPTION`, and so does, with `latin1` or `utf16le`, a `delimiter` or `rowDelimiter` with characters other than ASCII, because the bytes of a field could otherwise join the next field's (or the delimiter's) into a byte order mark or a formula sign. `utf16le` output starts with the UTF-16LE byte order mark FF FE (exceljs writes it only with `writeBOM: true`). LibreOffice 24.2.7.2 reads a UTF-16LE file as 8-bit text and drops the NUL bytes, with its default import and with charset UTF-8, also after that mark (only charset 65535, UTF-16, decodes it), and starts a formula only at a field beginning with `=` (or a quote and `=`); so with `utf16le` a field whose bytes without NULs begin so (for example `㴀㄀⬀㄀`, bytes 00 3D 00 31 …, or `丽`, 3D 4E) is escaped with `'`, and a field whose bytes there hold a delimiter (`,`, `;`, tab, `|`) or line break followed by `=`, through a character above U+00FF (`ਗ=1`, 17 0A …) or after a quote byte of one (`a∀,=1`; such a quote also counts for all later fields), throws `XLV_E_INVALID_OPTION` (`details.option` `encoding`, with the cell); CJK text whose bytes hold such characters without `=` after them is written unchanged. The mark's bytes come before the first field in that reading, so its opening quote is not taken as quoting there: a quoted first field holding a delimiter or line break followed by `=` (`x,=1+1`) throws `XLV_E_INVALID_OPTION` with `utf16le`, and with `latin1` and `writeBOM` (the byte FF); with UTF-8, whose mark LibreOffice drops, it is written. | `lib/csv/csv.js` L109–186 |
1349
+ | `csv.read(stream, options)`, `csv.readFile(path, options)` | supported | Adds a worksheet, with exceljs's options (`sheetName`, `dateFormats`, `map`, `parserOptions`: `delimiter`, `quote`, `escape`, `trim`/`ltrim`/`rtrim`, `ignoreEmpty`, `comment`, `skipLines`, `skipRows`, `maxRows`, `headers`, `renameHeaders`, `encoding`; `latin1` and `binary` map each byte to the code point of the same value, as Node.js's `StringDecoder` in fast-csv does, `@fast-csv/parse/build/src/CsvParserStream.js` L20) and its default conversion of fields (X4-23), on xlvane's own parser (no fast-csv). As with fast-csv, a line of white space only is a row without fields, for which `map` is not called (`@fast-csv/parse/build/src/parser/RowParser.js` L15-48). Accepts a Node.js readable stream, a Web `ReadableStream` or any (async) iterable of strings or bytes. Differences: text of white space only stays text (exceljs reads `' '` as 0), `-Infinity` stays text (it cannot be stored), a leading byte order mark is dropped, as in exceljs (fast-csv 4.3.6 removes it: `@fast-csv/parse/build/src/parser/Parser.js` L12-19 `removeBOM`, called by `parse` at L20-23), `headers: true` skips the header line (exceljs throws `data.map is not a function`), a missing file or a malformed line throws `XLV_E_FILE_READ` / `XLV_E_CSV_MALFORMED`. `readFile` needs `node:fs` (Node.js, Bun, Deno). Dates are parsed as dayjs 1.11 customParseFormat parses them in strict mode (D-E06): letters that are no token, such as an unbracketed `T` or `dddd`, are skipped, and each token is searched in the rest of the text. An unclosed quote is reported on the line where it opens; a `quote` or `escape` longer than one character throws `XLV_E_INVALID_OPTION`. Rows are added as they are parsed, as fast-csv streams them, so a malformed line leaves the rows before it in the worksheet. More than 1 048 576 rows, or a row of more than 16 384 fields, throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxRows` or `maxColumns`) as soon as parsing reaches it (exceljs collects the rows and fails with a plain `Error` in `addRow`); rows left out by `ignoreEmpty`, the header, `skipRows` and `maxRows` do not count (K2). More than 5 000 000 fields in the rows added (`DEFAULT_READ_LIMITS.maxCells`, the core's cell limit) throw `XLV_E_LIMIT_EXCEEDED` (`details.limit` `maxCells`, with the line) before any row is added: a text of 5 000 000 characters or more is counted in a first pass (K3; exceljs has no such limit, and 10 MB of `1,1,…` took 885 MB). | `lib/csv/csv.js` L32–98 |
1350
+ | `csv.write(stream, options)`, `csv.writeFile(path, options)`, `csv.writeBuffer(options)` | supported | Writes one worksheet with exceljs's options (`sheetName`, `sheetId`, `dateFormat`, `dateUTC`, `map`, `includeEmptyRows`, `encoding`, `formatterOptions`: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns` and `quoteHeaders` (`true`, an array, or an object of header names), `headers` (an array or `true`), `writeHeaders`, `alwaysWriteHeaders`, `includeEndRowDelimiter`, `writeBOM`, `transform`) and fast-csv's quoting and row handling (X4-24; `@fast-csv/format` 4.3.5 `build/src/formatter/RowFormatter.js`, `FieldFormatter.js`): `transform` gets each row (the mapped values, `[]` for a missing row) and may return an array, an object (written by the header names), a list of `[header, value]` pairs or nothing (no line); a transform that does not take exactly one parameter is called with a callback, as fast-csv does; the header line is written with the first row (none for a worksheet without rows, unless `alwaysWriteHeaders`); `headers: true` takes the worksheet's first row as the header line, and the blank lines for missing rows before it are left out (exceljs writes one fewer blank line before the first row, see below, so from a first row of 3 on its blank line becomes the header line and no data is written). Formulas are escaped in the fields that `transform` returns as in all others. `BOM` other than U+FEFF throws `XLV_E_NOT_SUPPORTED`; `objectMode` has no effect. The cross-check sample gives the same text as exceljs 4.4.0. Differences: text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10, CSV-12; `formulaInjection: "off"` writes it unchanged, as exceljs); a formula result of 0 or `false` is written (exceljs writes an empty field), a date result is formatted as a date and an error result as its text; one blank line per missing row also before the first row (exceljs writes one too few there); a `sheetName` or `sheetId` that does not exist throws `XLV_E_SHEET_NOT_FOUND` (exceljs writes an empty file); header names in `formatterOptions.headers` are escaped like values. With `headers`, an array row gives one field per header (longer rows cut, shorter ones end in empty fields), as fast-csv writes it; `dateFormat: ''` uses the default format, as dayjs does (D-E06). `writeFile` writes `utf8`, `utf16le` or `latin1` and needs `node:fs`; with `latin1` characters above U+00FF are written as `?` (Node.js, and so exceljs, keeps only their low byte, which can turn U+013D into `=`), and formatter options with such characters throw `XLV_E_INVALID_OPTION`. While formulas are escaped (the default), a `delimiter` holding a formula sign (`=`, `+`, `-`, `@`, their full-width or small forms) or a control character (a lone tab is allowed), a `rowDelimiter` other than `\n`, `\r\n` or `\r`, a `quote` other than `"` or `false`, an `escape` other than the quote, and with `quote: false` a field that starts with `"` throw `XLV_E_INVALID_OPTION`, because a standard CSV reader would otherwise get a field starting with a formula (docs/facts/csv.md CSV-13); `formulaInjection: "off"` writes them as exceljs does. Also while formulas are escaped (K2): text that starts with a byte order mark (U+FEFF or U+FFFE) is escaped, because applications drop a BOM at the start of a file and run what follows; with `encoding: "latin1"` or `"utf16le"` a field is also escaped when its first bytes as written are a BOM (EF BB BF, FF FE, FE FF, for example the latin1 text `=1+1`) or the UTF-8 form of a full-width or small formula sign, and with `latin1` and `writeBOM` (written as the byte FF) a first field starting with `þ`; a value from `map` that is not a number, boolean, `null` or `undefined` (a `String` object, an array, an object with `toString`) is turned into its text once and checked like text (fast-csv writes its text unchecked); a field holding a comma, `;`, tab or `|` is quoted whatever the delimiter (fast-csv quotes only the delimiter and `|`), because an application that splits on another of these characters would see a new field there, and with `quote: false` a field where one of them is followed by a formula sign (after spaces and an opening `"`) throws `XLV_E_INVALID_OPTION`. Also while formulas are escaped (K3): an empty `delimiter` throws `XLV_E_INVALID_OPTION`, and so does, with `latin1` or `utf16le`, a `delimiter` or `rowDelimiter` with characters other than ASCII, because the bytes of a field could otherwise join the next field's (or the delimiter's) into a byte order mark or a formula sign. `utf16le` output starts with the UTF-16LE byte order mark FF FE (exceljs writes it only with `writeBOM: true`). LibreOffice 24.2.7.2 reads a UTF-16LE file as 8-bit text and drops the NUL bytes, with its default import and with charset UTF-8, also after that mark (only charset 65535, UTF-16, decodes it), and starts a formula only at a field beginning with `=` (or a quote and `=`); so with `utf16le` a field whose bytes without NULs begin so (for example `㴀㄀⬀㄀`, bytes 00 3D 00 31 …, or `丽`, 3D 4E) is escaped with `'`, and a field whose bytes there hold a delimiter (`,`, `;`, tab, `|`) or line break followed by `=`, through a character above U+00FF (`ਗ=1`, 17 0A …) or after a quote byte of one (`a∀,=1`; such a quote also counts for all later fields), throws `XLV_E_INVALID_OPTION` (`details.option` `encoding`, with the cell); CJK text whose bytes hold such characters without `=` after them is written unchanged. The mark's bytes come before the first field in that reading, so its opening quote is not taken as quoting there: a quoted first field holding a delimiter or line break followed by `=` (`x,=1+1`) throws `XLV_E_INVALID_OPTION` with `utf16le`, and with `latin1` and `writeBOM` (the byte FF); with UTF-8, whose mark LibreOffice drops, it is written. | `lib/csv/csv.js` L109–186 |
1350
1351
  | `csv.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/csv/csv.js` L101 |
1351
1352
  | `definedNames` (`add`, `addEx`, `remove`, `removeEx`, `removeAllNames`, `forEach`, `getNames`, `getNamesEx`, `getRanges`, `getMatrix`, `model`) | supported | As in exceljs, a name is a set of cells, written as rectangles with absolute references and no sheet scope; on reading, names that are not cell ranges (constants, formulas, `#REF!`) and the sheet scope (`localSheetId`) are dropped. Built-in `_xlnm.` names are not listed: print areas and titles are read into `pageSetup`, as in exceljs, and xlvane writes the autofilter name itself. Names exceljs accepts but Excel does not (for example `A1` or names starting with `_xl`) throw `XLV_E_INVALID_DEFINED_NAME` when the workbook is written. | `lib/doc/defined-names.js`; `lib/xlsx/xform/book/defined-name-xform.js`; `lib/xlsx/xform/book/workbook-xform.js` L187 |
1352
1353
  | `addImage(image)`, `getImage(id)` | supported | PNG, JPEG and GIF given as `buffer`, `base64` (a data URL is accepted, as in exceljs) or `filename`; the format is recognised from the bytes, `extension` is kept but not used (exceljs writes `image/jpg` for `extension: 'jpg'`). As in exceljs, a `filename` is read when the workbook is written (`xlsx.writeBuffer`/`writeFile`/`write` and the streaming `WorkbookWriter`), so a missing file fails there; outside Node.js, Bun and Deno it gives `XLV_E_NOT_SUPPORTED`. Other formats throw `XLV_E_INVALID_IMAGE`. Read back, each image part is one media entry `{ type, name, extension, buffer }`, `buffer` a `Uint8Array`. Identical images are stored once, with one relationship per drawing (exceljs writes one per use). | `lib/doc/workbook.js` L142–151; `lib/xlsx/xlsx.js` L421–442 (L426–429: `filename` read at write) |
@@ -1364,12 +1365,12 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1364
1365
  | `eachRow({ includeEmpty }, cb)` | supported | | `lib/doc/worksheet.js` L551 |
1365
1366
  | `getSheetValues()` | supported | Every row object, also an empty one (`[]`), as in exceljs. | `lib/doc/worksheet.js` L571–579 |
1366
1367
  | `rowCount`, `actualRowCount`, `columnCount` | supported | | `lib/doc/worksheet.js` L374, L378, L313 |
1367
- | `mergeCells(range \| t, l, b, r)`, `unMergeCells` | partly | Styles of merged cells differ, see F1 and F2 under [Known differences planned for 1.3.1](#known-differences-planned-for-131). | `lib/doc/worksheet.js` L602, L656 |
1368
+ | `mergeCells(range \| t, l, b, r)`, `unMergeCells` | supported | As in exceljs, the other cells of the range share the top-left cell's style object, so a style set on any cell of the range afterwards (`cell.fill = ...`) shows on all of them and is written for all of them; assigning `cell.style` gives one cell an object of its own. `unMergeCells` gives the other cells the row and column style again. | `lib/doc/worksheet.js` L602, L656; `lib/doc/cell.js` L169-183 |
1368
1369
  | `mergeCellsWithoutStyle(...)`, `findRows(start, length)`, `lastColumn`, `fillFormula(range, formula, results, shareType)`, `model` (get / set), `destroy()` | supported | `fillFormula` writes the formula into the top-left cell and `{ sharedFormula }` (or the same array formula) into the others, with results from a function, a flat array or a 2-D array, as in exceljs (X4-29). `findRows` returns `_rows.slice(start - 1, start - 1 + length)` as exceljs does (D-E06). `model` is xlvane's own plain snapshot (see `workbook.model`). | `lib/doc/worksheet.js` L181, L309, L370, L607, L678–721, L842–903 |
1369
- | `views` | partly | The first view is written: frozen panes, split panes (`xSplit`/`ySplit` in 1/20 point), `zoomScale` (10–400), `zoomScaleNormal`, `showGridLines`, `showRowColHeaders`, `showRuler`, `rightToLeft`, `style` (`pageBreakPreview`, `pageLayout`), `activeCell`, and `topLeftCell` of a normal view (X4-28). Differences: further views are not written, because xlvane's core writes one `sheetView` per worksheet; `workbookViewId` is always 0; frozen panes always get the cell right of and below the panes as `topLeftCell` (exceljs writes a frozen view's own `topLeftCell` when given, sheet-view-xform.js L50-53); a split view's `topLeftCell` is written as `pane@topLeftCell` and read back, as in exceljs (L72-85, L118-124; D-F02), and its `activeCell` is written and read back as the selection of the active pane; `activePane` is the pane the split implies; `tabSelected` is written for the first visible worksheet, whatever the view says. Every worksheet reads back with one view shaped as exceljs reads it. | `lib/doc/worksheet.js` L114; `lib/xlsx/xform/sheet/sheet-view-xform.js`; `index.d.ts` L33–149 |
1370
+ | `views` | partly | The first view is written: frozen panes, split panes (`xSplit`/`ySplit` in 1/20 point), `zoomScale` (10–400), `zoomScaleNormal`, `showGridLines`, `showRowColHeaders`, `showRuler`, `rightToLeft`, `style` (`pageBreakPreview`, `pageLayout`), `activeCell`, and `topLeftCell` of a normal view (X4-28). Differences: further views are not written, because xlvane's core writes one `sheetView` per worksheet; `workbookViewId` is always 0; frozen panes always get the cell right of and below the panes as `topLeftCell` (exceljs writes a frozen view's own `topLeftCell` when given, sheet-view-xform.js L50-53), and a file's `pane@topLeftCell` of frozen panes is not read (exceljs reads it, L157); a frozen view whose active cell is that cell right of and below the panes reads without `activeCell`, because xlvane writes that cell as the active cell when none is given (exceljs writes none then, L65-69, and reads the active pane's selection, L167-170); a split view's `topLeftCell` is written as `pane@topLeftCell` and read back, as in exceljs (L72-85, L118-124; D-F02), and its `activeCell` is written and read back as the selection of the active pane; `activePane` is the pane the split implies; `tabSelected` is written for the first visible worksheet, whatever the view says. Every worksheet reads back with one view shaped as exceljs reads it. | `lib/doc/worksheet.js` L114; `lib/xlsx/xform/sheet/sheet-view-xform.js`; `index.d.ts` L33–149 |
1370
1371
  | `autoFilter` (range string) | supported | | `lib/doc/worksheet.js` L116 |
1371
1372
  | `insertRow(pos, values, style?)`, `insertRows(pos, values, style?)` | partly | Style options `n`, `i`, `i+`, `o`, `o+` as in exceljs. Built on `spliceRows`, so the differences below apply. | `lib/doc/worksheet.js` L422, L428, L444 |
1372
- | `spliceRows(start, count, ...inserts)` | partly | xlvane moves rows as a spreadsheet application does; exceljs 4.4.0 moves only values, styles and heights (X4-15). Differences: row objects keep their identity and their `number` follows them (exceljs copies values between row objects); the hidden flag, notes and links move with the row (exceljs leaves hidden/outline at the position and drops notes); merged ranges move, grow when rows are inserted inside them (inserted cells get a copy of the top-left cell's style, the other cells keep their own), shrink when rows are removed, and are dropped when their top-left cell is removed or one cell or nothing is left, as LibreOffice does (R-14; exceljs keeps stale ranges and dissolves merges on removal); the autofilter range moves and shrinks, and stops at the last row or column (exceljs leaves it); cells inserted inside a defined name join it (exceljs leaves a gap), and names follow whatever the case of their sheet name (exceljs compares it exactly); styles are copied, never shared; removing more rows than follow the removed block works (exceljs leaves rows behind). Formulas in every worksheet follow the splice as in a spreadsheet application (xlvane `adjustFormula`, D-B01; exceljs leaves them unchanged); formulas given in the inserted rows keep the text the caller wrote. If a formula cannot be adjusted safely, the call throws `XLV_E_NOT_SUPPORTED` before anything changes. | `lib/doc/worksheet.js` L483–548 |
1373
+ | `spliceRows(start, count, ...inserts)` | partly | xlvane moves rows as a spreadsheet application does; exceljs 4.4.0 moves only values, styles and heights (X4-15). Differences: row objects keep their identity and their `number` follows them (exceljs copies values between row objects); the hidden flag, notes and links move with the row (exceljs leaves hidden/outline at the position and drops notes); merged ranges move, grow when rows are inserted inside them (inserted cells get a copy of the top-left cell's style, the other cells keep their own), shrink when rows are removed, and are dropped when their top-left cell is removed or one cell or nothing is left, as LibreOffice does (R-14; exceljs keeps stale ranges and dissolves merges on removal); the autofilter range moves and shrinks, and stops at the last row or column (exceljs leaves it); cells inserted inside a defined name join it (exceljs leaves a gap), and names follow whatever the case of their sheet name (exceljs compares it exactly); styles are copied, never shared (the cells of a merged range keep the style object they share with the top-left cell, see `mergeCells`); removing more rows than follow the removed block works (exceljs leaves rows behind). Formulas in every worksheet follow the splice as in a spreadsheet application (xlvane `adjustFormula`, D-B01; exceljs leaves them unchanged); formulas given in the inserted rows keep the text the caller wrote. If a formula cannot be adjusted safely, the call throws `XLV_E_NOT_SUPPORTED` before anything changes. | `lib/doc/worksheet.js` L483–548 |
1373
1374
  | `duplicateRow(rowNum, count, insert = false)` | partly | The default of `insert` is `false`, as in exceljs's code (its README says `true`). Copies get the values, styles (copied, not shared) and height; merged ranges inside the row are copied too; a merged range's other cells stay merged instead of getting the master value (exceljs); a formula is copied as a spreadsheet application copies a cell: its relative references move by the distance of the copy (xlvane `shiftFormula`; exceljs copies the text). With `insert = true` the rows below move first and every formula follows (`spliceRows`), so a copy of `A5` in row 1 reads `A6` in row 1 and `A7` in row 2. With `insert = false` the rows below are overwritten in place: merged ranges that touch them are removed and no other formula changes (exceljs implements this as a removal plus an insertion). | `lib/doc/worksheet.js` L463 |
1374
1375
  | `spliceColumns(start, count, ...inserts)` | partly | Each insert is an array of values by row (index 0 is row 1). Columns move with their cells, widths, styles, keys and notes; merged ranges, the autofilter, defined names and formulas follow, as for `spliceRows`. Unlike exceljs, falsy values such as `0` and `false` are kept, values beyond the existing rows are written, headers are not written again into row 1, and the last column does not get width 9. | `lib/doc/worksheet.js` L264–307 |
1375
1376
  | Invalid arguments of the row and column methods | partly | Arguments exceljs accepts and then loses or misplaces data with (`insertRow(0)`, `insertRow('2')`, a negative count, `duplicateRow` of a missing row or without a count, `insertRows` without an array, rows or columns pushed, or inserted, beyond row 1048576 or column XFD) throw `XLV_E_INVALID_ARGUMENT` before anything changes. | `lib/doc/worksheet.js` L422–548 |
@@ -1390,7 +1391,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1390
1391
  | `eachCell({ includeEmpty }, cb)` | supported | As in exceljs, cells of a merged range give the master cell's value. | `lib/doc/row.js` L130 |
1391
1392
  | `height`, `hidden` | supported | | `lib/doc/row.js` L310 |
1392
1393
  | `outlineLevel`, `collapsed`, `protection`, `getCellEx(address)` | supported | `outlineLevel` (0–7, [MS-OI29500] §2.1.644) is written and read back; a level outside 0–7 throws `XLV_E_INVALID_ARGUMENT` when it is set (exceljs writes it; D-E06); `collapsed` is derived from the level and `properties.outlineLevelRow`, as in exceljs (X4-28). | `lib/doc/row.js` L45, L286–330 |
1393
- | `font`, `fill`, `border`, `alignment`, `numFmt` setters | partly | Apply to the row's cells. The row's own style is not written or read (F3 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)). | `lib/doc/row.js` L274, L306 |
1394
+ | `font`, `fill`, `border`, `alignment`, `numFmt` setters | supported | Apply to the row's existing cells and to `row.style`, from which cells created later in the row take their style, as in exceljs. `row.style` is written as `<row s="..." customFormat="1">` for the rows exceljs writes, those with a height or cells (a row with only a style is not written, `lib/doc/row.js` L333-364; xlvane also writes hidden and outlined rows), and only with styles (not with `useStyles: false`); the streaming writer writes it too. `xlsx.load`, and the streaming reader with `styles: 'cache'`, read it back as `row.style`. Difference: a row's `s` is read only when the row has `customFormat="1"`, without which it does not apply (ECMA-376 Part 1 §18.3.1.73); exceljs reads `s` without that check (`row-xform.js` L80-82). `xlsx.load` reads row settings only up to the sheet's dimension or last cell (see [Writing workbooks that were read from files](#writing-workbooks-that-were-read-from-files)). | `lib/doc/row.js` L252-306; `lib/xlsx/xform/sheet/row-xform.js` L19-23, L44-47, L80-82, L130 |
1394
1395
  | `commit()` | supported | Meaningful for `WorkbookWriter`; does nothing in a normal workbook, as in exceljs. | `lib/doc/row.js` L29 |
1395
1396
  | `cellCount`, `actualCellCount` | supported | | `lib/doc/row.js` L216, L220 |
1396
1397
  | `splice(start, count, ...inserts)` | partly | Cells move with their values, styles and notes, as in exceljs. A row that crosses a merged range at or right of `start` throws `XLV_E_NOT_SUPPORTED` (the range cannot follow the cells of one row; exceljs gives wrong values). Removed cells at the end are removed, not set to `null`. Formulas are not rewritten, as in exceljs. | `lib/doc/row.js` L77–127 |
@@ -1404,17 +1405,17 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1404
1405
  | `outlineLevel`, `collapsed`, `protection`, `isDefault`, `toString()`, `equivalentTo(other)` | supported | `outlineLevel` (0–7) is written and read back, and a level outside 0–7 throws `XLV_E_INVALID_ARGUMENT` when it is set (D-E06); `collapsed` is derived from the level and `properties.outlineLevelCol`, as in exceljs (X4-28). | `lib/doc/column.js` L116–147, L238 |
1405
1406
  | `letter`, `number` | supported | | `lib/doc/column.js` L23, L31 |
1406
1407
  | `values` | supported | | `lib/doc/column.js` L179 |
1407
- | `eachCell({ includeEmpty }, cb)` | partly | Without `includeEmpty`, only cells with values are visited (F12 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)). | `lib/doc/column.js` L168 |
1408
- | `font`, `fill`, `border`, `alignment`, `numFmt` setters, `style` | supported | Applied to the column's existing cells, inherited by cells added later (the row style wins), and written as the column's default style (`col@style`). As in exceljs, a styled column without a width is written with width 9, and a column with width 9 and nothing else is not written. The streaming writer writes the column style only with `useStyles: true`. | `lib/doc/column.js` L147, L206, L265 |
1408
+ | `eachCell({ includeEmpty }, cb)` | supported | As in exceljs, the column's cell of each row that `worksheet.eachRow` visits (the rows with values, or every row up to the last with `includeEmpty`), created when it does not exist yet. | `lib/doc/column.js` L168-177 |
1409
+ | `font`, `fill`, `border`, `alignment`, `numFmt` setters, `style` | supported | Applied, as in exceljs, to the column's cell of each row with values (created when needed; `lib/doc/column.js` L206-212 through `eachCell`), inherited by cells added later (the row style wins), and written as the column's default style (`col@style`). As in exceljs, a styled column without a width is written with width 9, and a column with width 9 and nothing else is not written. The streaming writer writes the column style only with `useStyles: true`. | `lib/doc/column.js` L147, L206, L265 |
1409
1410
 
1410
1411
  ## Cell values
1411
1412
 
1412
1413
  | Value | Status | Notes | Source in exceljs |
1413
1414
  |---|---|---|---|
1414
1415
  | `null`, number, string, boolean | supported | | `lib/doc/cell.js` L206, L211 |
1415
- | `Date` | partly | Without `numFmt`, a date (also a formula result that is a date) is written with built-in format 14, as exceljs does, also by the streaming writer without `useStyles`; applications show it in the reader's date order, and reading gives `numFmt` `"mm-dd-yy"`. Time-only values, dates before 1900-03-01, upper-case date formats and dates outside 1900-9999 differ, see F6, F7 and F8 under [Known differences planned for 1.3.1](#known-differences-planned-for-131). | `lib/doc/cell.js` L538; `lib/xlsx/xform/style/styles-xform.js` L253–266, L498–515 |
1416
+ | `Date` | partly | Without `numFmt`, a date (also a formula result that is a date) is written with built-in format 14, as exceljs does, also by the streaming writer without `useStyles`; applications show it in the reader's date order, and reading gives `numFmt` `"mm-dd-yy"`. A time-only value given as exceljs does (`new Date(Date.UTC(1899, 11, 30, 13, 20))`) is written as exceljs writes it, serial 0.5555... (`lib/utils/utils.js` L55-57 `dateToExcel`), and a serial below 1 with a date format reads as 1899-12-30 plus the time (L58-61 `excelToDate`). A number reads as a `Date` when exceljs takes its format for a date format (L140-151 `isDateFmt`: one of the letters y, m, d, h, M, s, b outside brackets and quotes), so also with upper-case formats such as `DD.MM.YYYY`, in `xlsx.load` and, except for formula results, in the streaming reader, as exceljs's `cell-xform.js` L449-459 and `worksheet-reader.js` L329-334 do. Dates from 1900-01-01 to 1900-02-28 and dates outside 1900-9999 differ, see F6 and F8 under [Known differences from the 1.3.0 review](#known-differences-from-the-130-review). | `lib/doc/cell.js` L538; `lib/xlsx/xform/style/styles-xform.js` L253–266, L498–515 |
1416
1417
  | `{ formula, result }` | supported | xlvane does not calculate; `result` is stored as given. | `lib/doc/cell.js` L734 |
1417
- | `{ sharedFormula, result }`, `{ formula, shareType, ref, result }`, `formulaType` | supported | Shared formulas are written as `t="shared"` groups: the master's `ref` is its own `ref` or grows to its last dependent, as in exceljs; array formulas (`shareType: 'array'`) are written with their range (X4-29). Read back as exceljs reads them: the master `{ formula, shareType: 'shared', ref, result }`, a dependent `{ sharedFormula, result }` whose `cell.formula` is the translated formula, moved from the master when `cell.formula` is read (loading does not build the dependents' texts, which a huge master shared with many cells made exhaust memory; K2). The moved text is kept on the dependent cell until its value, its master's formula or the position of either changes (exceljs keeps it per value, `lib/doc/cell.js` L861-869, and does not see a later change of the master's formula). The shared-formula text moved in one workbook (each master text read plus each moved text built) may not pass 32 Mi characters plus 256 for each move, so formulas of up to 128 characters never reach the limit; a move past it throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `sharedFormulaText`, with the dependent's location) from `cell.formula`, because each move costs time in proportion to the master's length: a 4 KB file sharing a 1 000 000-character master with 250 cells took about 47 s to read every dependent's formula once (K4 S1). exceljs has no such limit. A dependent the master's formula cannot be moved to (outside the master's range, or a formula xlvane cannot move safely) is read as its cached value (exceljs reads `{ sharedFormula, result }`). `formulaType` is 0 (none), 1 (master) or 2 (shared). The streaming `WorkbookWriter` writes a dependent of a master with `ref` as a shared formula, and any other `{ sharedFormula }` cell as its translated formula, which needs the master's row to be uncommitted, or a dependent from the streaming `WorkbookReader`: it is `{ sharedFormula, result }` as above, with its translated formula as a non-enumerable `formula` built when it is read (not in `Object.keys` or JSON), so copying its rows with `addRow(row.values).commit()` writes each dependent as its formula (K3). Differences: xlvane refuses (`XLV_E_INVALID_VALUE`) what exceljs writes against the schema rules of Part 1 §18.3.1.40: overlapping shared ranges, a dependent outside its master's range, and a formula in another cell of an array formula's range. A splice (`spliceRows`, `spliceColumns`, `insertRow(s)`, `duplicateRow` with insertion) turns every shared group of the workbook into plain formulas, each adjusted for the splice, because moving the master's text to each dependent would give wrong references; array formulas keep their group and their range moves (D-E06). A `$` in a shared master's `ref` (exceljs `fillFormula` over an absolute range) is read as the same range without `$`. | `lib/doc/cell.js` L744, L811; `lib/xlsx/xform/sheet/cell-xform.js` L94–160, L460–470 |
1418
+ | `{ sharedFormula, result }`, `{ formula, shareType, ref, result }`, `formulaType` | supported | Shared formulas are written as `t="shared"` groups: the master's `ref` is its own `ref` or grows to its last dependent, as in exceljs; array formulas (`shareType: 'array'`) are written with their range (X4-29). Read back as exceljs reads them: the master `{ formula, shareType: 'shared', ref, result }`, a dependent `{ sharedFormula, result }` whose `cell.formula` is the translated formula, moved from the master when `cell.formula` is read (loading does not build the dependents' texts, which a huge master shared with many cells made exhaust memory; K2). The moved text is kept on the dependent cell until its value, its master's formula or the position of either changes (exceljs keeps it per value, `lib/doc/cell.js` L861-869, and does not see a later change of the master's formula). The shared-formula text moved in one workbook (each master text read plus each moved text built) may not pass 32 Mi characters plus 256 for each move, so formulas of up to 128 characters never reach the limit; a move past it throws `XLV_E_LIMIT_EXCEEDED` (`details.limit` `sharedFormulaText`, with the dependent's location) from `cell.formula`, because each move costs time in proportion to the master's length: a 4 KB file sharing a 1 000 000-character master with 250 cells took about 47 s to read every dependent's formula once (K4 S1). exceljs has no such limit. A dependent the master's formula cannot be moved to (outside the master's range, or a formula xlvane cannot move safely) is read as its cached value (exceljs reads `{ sharedFormula, result }`). `formulaType` is 1 (master) or 2 (shared) for a formula, and `undefined` for a cell without one and for the other cells of a merged range, as in exceljs (`lib/doc/cell.js` L264-266, L807-815; exceljs's declaration says `FormulaType`). The streaming `WorkbookWriter` writes a dependent of a master with `ref` as a shared formula, and any other `{ sharedFormula }` cell as its translated formula, which needs the master's row to be uncommitted, or a dependent from the streaming `WorkbookReader`: it is `{ sharedFormula, result }` as above, with its translated formula as a non-enumerable `formula` built when it is read (not in `Object.keys` or JSON), so copying its rows with `addRow(row.values).commit()` writes each dependent as its formula (K3). Differences: xlvane refuses (`XLV_E_INVALID_VALUE`) what exceljs writes against the schema rules of Part 1 §18.3.1.40: overlapping shared ranges, a dependent outside its master's range, and a formula in another cell of an array formula's range. A splice (`spliceRows`, `spliceColumns`, `insertRow(s)`, `duplicateRow` with insertion) turns every shared group of the workbook into plain formulas, each adjusted for the splice, because moving the master's text to each dependent would give wrong references; array formulas keep their group and their range moves (D-E06). A `$` in a shared master's `ref` (exceljs `fillFormula` over an absolute range) is read as the same range without `$`. | `lib/doc/cell.js` L744, L811; `lib/xlsx/xform/sheet/cell-xform.js` L94–160, L460–470 |
1418
1419
  | A style on a cell without a value | supported | Written and read back: xlvane's reader keeps cells that hold only a style (D-E05). | `lib/xlsx/xform/sheet/cell-xform.js` |
1419
1420
  | `{ error: '#N/A' }` | supported | | `lib/doc/cell.js` L970 |
1420
1421
  | Other objects and arrays (JSON values) | supported | Kept on the cell with type `String` and written as their JSON text, as exceljs; read back as that text. | `lib/doc/cell.js` L1014–1054 |
@@ -1432,12 +1433,12 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1432
1433
  | API | Status | Notes | Source in exceljs |
1433
1434
  |---|---|---|---|
1434
1435
  | `numFmt` | supported | Codes that exceljs maps to built-in formats (`'0.00'`, `'mm-dd-yy'`, `'@'`, …) are written as the built-in id, as exceljs does. | `lib/doc/cell.js` L51; `lib/xlsx/defaultnumformats.js`; `lib/xlsx/xform/style/styles-xform.js` L374 |
1435
- | `font` | supported | All exceljs font properties are written and read back (`vertAlign`, `family`, `scheme`, `charset`, `outline`, `shadow`, `condense`, `extend` too; X4-27). A font without `name` or `size` is written with the workbook's default font for the missing ones (Calibri 11), so it reads back with them; exceljs writes only what is given. A single underline (`underline: true` or `'single'`) is written as `<u/>` and reads back as `true`, as in exceljs (`underline-xform.js` L14–30, L40–45); a file with `<u val="single"/>`, which exceljs reads as `'single'`, also reads back as `true` here. | `lib/doc/cell.js` L59; `lib/xlsx/xform/style/font-xform.js`; `index.d.ts` L217 |
1436
+ | `font` | supported | All exceljs font properties are written and read back (`vertAlign`, `family`, `scheme`, `charset`, `outline`, `shadow`, `condense`, `extend` too; X4-27). A font without `name` or `size` is written with the workbook's default font for the missing ones (Calibri 11), so it reads back with them; exceljs writes only what is given. The workbook's default font (font 0, the font of cells without a style) of a file read with `xlsx.load` is written again as font 0, for example Arial 10, so its cells keep it; parts of it the writer does not accept (a name of more than 31 characters or an empty one, a size outside 1 to 409.55 points) are left out, so such a file is still written; a workbook made in code, and the streaming writer, write Calibri 11. exceljs always writes Calibri 11 as font 0, so after its load and write cells without a style show Calibri 11. As in exceljs, a style read from a file (a cell with `s` of 1 or more, a row or column style) has the font its `fontId` names also when that is font 0 (`lib/xlsx/xform/style/styles-xform.js` L296-345), so `cell.font` is, for example, `{ name: 'Arial', size: 10 }`; a cell with `s="0"` or no `s` has none (`lib/xlsx/xform/sheet/cell-xform.js` L428-432). As in exceljs, a column or row with style 0 (`<col style="0">`, which LibreOffice writes on every column, or `<row s="0" customFormat="1">`) has no style (`col-xform.js` L80, `row-xform.js` L130); xlvane's core gives no style index for columns and rows, so a column or row style equal to xf 0 (the style of a cell without `s`; when no worksheet has such a cell, xf 0 is not known and style 0 reads like other styles) counts as style 0, and also a row or column style with an `s` of 1 or more whose cell format equals xf 0, or that has nothing but font 0, reads without a font. The streaming reader does not add font 0 (the core's streaming reader does not give the default font). A single underline (`underline: true` or `'single'`) is written as `<u/>` and reads back as `true`, as in exceljs (`underline-xform.js` L14–30, L40–45); a file with `<u val="single"/>`, which exceljs reads as `'single'`, also reads back as `true` here. | `lib/doc/cell.js` L59; `lib/xlsx/xform/style/font-xform.js`; `index.d.ts` L217 |
1436
1437
  | `fill` (pattern and gradient) | supported | Gradient fills: `gradient: 'angle'` with `degree` is written as a linear gradient, `gradient: 'path'` with `center` (`right`/`bottom` default to `left`/`top`, as exceljs) and the stops; read back in exceljs's shape (X4-27). | `lib/doc/cell.js` L83; `lib/xlsx/xform/style/fill-xform.js`; `index.d.ts` L161–215 |
1437
1438
  | `border` (`top`, `left`, `bottom`, `right`, `diagonal`) | supported | `diagonal` with `up`, `down`, `style` and `color`, as in exceljs (X4-27). | `lib/doc/cell.js` L75; `lib/xlsx/xform/style/border-xform.js`; `index.d.ts` L258 |
1438
1439
  | `alignment` | supported | `horizontal`, `vertical`, `wrapText`, `shrinkToFit`, `indent`, `readingOrder` (`'ltr'`/`'rtl'`) and `textRotation` (`'vertical'` is 255, −90…−1 are 91…180, as exceljs writes them; X4-27). `textRotation` and `indent` follow exceljs's `validInt` (D-E06): numbers are truncated and numeric strings accepted; a rotation outside −90…90 (other than `'vertical'`) is dropped and an indent below 0 becomes 0, as in exceljs. An indent above 255 in a loaded file (more than Office accepts) is left out, so the workbook can be written again. | `lib/doc/cell.js` L67; `lib/xlsx/xform/style/alignment-xform.js`; `index.d.ts` L280 |
1439
1440
  | `protection` (`locked`, `hidden`) of cells, rows, columns and styles | supported | Written when it differs from the defaults (`locked: true`, `hidden: false`), as in exceljs (X4-27). | `lib/xlsx/xform/style/protection-xform.js` |
1440
- | Colours `{ argb }`, `{ theme, tint }`, `{ indexed }` | partly | A six-digit `rgb` read from a file is dropped (F5 under [Known differences planned for 1.3.1](#known-differences-planned-for-131)). | `index.d.ts` |
1441
+ | Colours `{ argb }`, `{ theme, tint }`, `{ indexed }` | partly | A six-digit `rgb` read from a file (`<color rgb="AA0000"/>`) reads as the opaque colour `{ argb: 'FFAA0000' }` and is written back with these 8 digits; exceljs reads `{ argb: 'AA0000' }` and writes the 6 digits back (`lib/xlsx/xform/style/color-xform.js` L19-20, L39-40). A six-digit `argb` set in code is likewise written as `FF` followed by the six digits; exceljs writes the six digits. | `index.d.ts`; `lib/xlsx/xform/style/color-xform.js` |
1441
1442
 
1442
1443
  ## Streaming
1443
1444
 
@@ -1496,24 +1497,12 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
1496
1497
 
1497
1498
  - None so far for what the sample reads back. The sample covers document properties (including `company` and `manager`), rich text, a column style, defined names (including `cell.name`), the built-in date format, a tab colour, a zoomed frozen view external, `mailto:` and `#` links (without tooltips, which exceljs does not read back), gradient fills, a diagonal border, alignment (`textRotation`, `shrinkToFit`, `readingOrder`) and protection, a shared formula from `fillFormula`, outline levels of rows and columns, workbook views, and `insertRow` with style `'i'`, `spliceRows`, `duplicateRow`, `spliceColumns`, `insertRows` and `row.splice` on plain values (no formulas, merges, notes or names, which exceljs does not move); CSV written with the default and with custom options and read back (no formula-like text, no formula result 0, no gap before the first row); `cross-check.test.ts` and `alias.test.ts` also run the documented deep imports (`deep-imports.cjs`); `alias.test.ts` also checks that LibreOffice shows the dates as exceljs's files do (`1/31/2024`).
1498
1499
 
1499
- ## Known differences planned for 1.3.1
1500
+ ## Known differences from the 1.3.0 review
1500
1501
 
1501
- Found by a review of 1.3.0 and checked by running exceljs 4.4.0 and `@xlvane/compat` on the same input (Node.js 22.23). They are not fixed yet.
1502
+ Found by a review of 1.3.0 and checked by running exceljs 4.4.0 and `@xlvane/compat` on the same input (Node.js 22.23). F6 describes a difference that xlvane keeps, and F8 values that xlvane refuses. F1, F2, F3, F4, F5, F7, F9, F10 and F12 of that review (merged-cell styles, row styles, the default font, six-digit colours, upper-case date formats, CSV formatter options, `latin1` CSV reading, `column.eachCell`) are fixed in 1.3.1 and described in the tables above, with the differences that remain.
1502
1503
 
1503
- - **F1** A style set on the top-left cell after `mergeCells` (for example `ws.getCell('A1').fill = ...`) does not reach the other cells of the range in memory: `B2.fill` stays `undefined`. exceljs gives every merged cell the master's style object (`lib/doc/cell.js` L168-174, `this.style = master.style`), so a later change of the master's `fill` or `font` shows on all of them. xlvane copies the master's style when merging (`packages/compat/src/model.ts`, `mergeCells`). The written file is the same: the writer gives merged cells the master's style.
1504
- - **F2** `xlsx.load` gives every cell of a merged range the top-left cell's style, replacing the style each cell has in the file (for example a right border on the last cell). exceljs merges loaded ranges without styles (`lib/doc/worksheet.js` L895-901 `_parseMergeCells` calls `mergeCellsWithoutStyle`), so each cell keeps its own. xlvane calls `mergeCells` there (`packages/compat/src/workbook.ts`, `_fill`).
1505
- - **F3** Row styles (`row.fill`, `row.font` and the other row style setters) apply to the row's existing cells only. exceljs also writes the row's own style as `<row s="..." customFormat="1">` (`lib/xlsx/xform/sheet/row-xform.js` L21-23, L44-46) and reads it back as `row.style` (L81, L130), from which cells created later in the row take their style. xlvane neither writes nor reads it.
1506
- - **F4** A cell whose style uses the workbook's default font (font 0, for example Arial 10, with a number format of its own) reads with `font` `undefined`, and after `xlsx.load` and `writeBuffer` it shows Calibri 11. exceljs reads font 0 into `cell.font` (`lib/xlsx/xform/style/styles-xform.js` L318-327, `addStyle` also for id 0) and writes it back as a font of its own. Cells without a style (`s="0"`) are written with Calibri 11 by both.
1507
- - **F5** A six-digit `rgb` colour in a file's styles (`<color rgb="AA0000"/>`) is dropped on reading: the font reads as `{}`. exceljs reads it as `{ argb: 'AA0000' }` (`lib/xlsx/xform/style/color-xform.js` L39-40) and writes it back unchanged (L19-20). When a six-digit `argb` is set in code, xlvane writes `FF` followed by the six digits; exceljs writes the six digits.
1508
- - **F6** Dates near the start of the 1900 date system:
1509
- - A time-only date such as `new Date(Date.UTC(1899, 11, 30, 13, 20))` throws `XLV_E_INVALID_VALUE` when written ("outside the 1900 date system"); exceljs writes serial 0.5555... (`lib/utils/utils.js` L55-57 `dateToExcel`).
1510
- - A time-only serial (below 1) with a date format reads as 1899-12-31 plus the time; exceljs gives 1899-12-30 plus the time (`lib/utils/utils.js` L58-61 `excelToDate`, which counts from 1899-12-30).
1511
- - Dates from 1900-01-01 to 1900-02-28 differ by one day. xlvane writes and reads them with serial 1 = 1900-01-01 (ECMA-376 Part 4 §15.3.4, docs/facts/ecma-376.md E-15); exceljs writes 1900-01-15 as serial 16 and reads serial 15 as 1900-01-14 (same functions), so here exceljs is the one that is off. From 1900-03-01 on both agree.
1512
- - **F7** A number whose format uses upper-case date letters (`DD.MM.YYYY`, `YYYY-MM-DD`, `HH:MM`) reads as a number. exceljs reads it as a `Date`: its test is `/[ymdhMsb]+/` after removing quoted and bracketed parts (`lib/utils/utils.js` L140-151 `isDateFmt`), which the upper-case `M` matches.
1504
+ - **F6** Dates from 1900-01-01 to 1900-02-28 differ by one day. xlvane writes and reads them with serial 1 = 1900-01-01 (ECMA-376 Part 4 §15.3.4, docs/facts/ecma-376.md E-15); exceljs writes 1900-01-15 as serial 16 and reads serial 15 as 1900-01-14 (`lib/utils/utils.js` L55-61), so here exceljs is the one that is off, and xlvane keeps its reading. From 1900-03-01 on both agree. Serial 60 (the 29 February 1900 that does not exist) and negative serials with a date format read as numbers (exceljs gives 1900-02-28 and dates before 1899-12-30). In a workbook with the 1904 date system a `Date` before 1904-01-01, such as a time-only value given as 1899-12-30 plus the time, throws `XLV_E_INVALID_VALUE` when written; exceljs writes a negative serial (-1461.44... for 1899-12-30T13:20). Time-only values in the 1900 date system and upper-case date formats such as `DD.MM.YYYY` (F6 and F7 of the 1.3.0 review) behave as in exceljs from 1.3.1.
1513
1505
  - **F8** Values exceljs writes without checks (`lib/xlsx/xform/sheet/cell-xform.js` L199-250, `render`) that xlvane refuses with `XLV_E_INVALID_VALUE` when the workbook is written: `NaN` and `Infinity` (exceljs writes `<v>NaN</v>`, `<v>Infinity</v>`), an invalid `Date` (exceljs writes `<v>NaN</v>`), dates before 1900-01-01 or after 9999-12-31 (exceljs writes the serial, for example -18260 for 1850-01-01), text of more than 32 767 characters, text with an unpaired UTF-16 surrogate, and formulas of more than 8 192 characters.
1514
- - **F9** `csv.write` and `csv.writeBuffer` ignore, without an error, three fast-csv 4.3 formatter options that exceljs passes on (`lib/csv/csv.js` L118 `fastCsv.format(options.formatterOptions)`): `transform` (`@fast-csv/format/build/src/formatter/RowFormatter.js` L22-23), `quoteHeaders`, and `quoteColumns` given as an object of header names (`@fast-csv/format/build/src/formatter/FieldFormatter.js` L24-35 `shouldQuote`). `quoteColumns` as `true` or as an array works.
1515
- - **F10** `csv.read` with `parserOptions.encoding: 'latin1'` decodes with the WHATWG `TextDecoder('latin1')`, which is windows-1252: the bytes 80 and 93 become U+20AC and U+201C. exceljs, through fast-csv's Node.js `StringDecoder` (`@fast-csv/parse/build/src/CsvParserStream.js` L20), gives U+0080 and U+0093.
1516
- - **F12** `column.eachCell(cb)` without `includeEmpty` visits only the column's cells that have values. exceljs visits the column's cell in every row that has a value in any column (`lib/doc/column.js` L168-177, through `worksheet.eachRow`), so an empty cell next to a filled one is visited too (and created).
1517
1506
 
1518
1507
  <!-- file: docs/migrate-from-exceljs.md -->
1519
1508
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlvane",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Read, inspect and write XLSX files; input is treated as untrusted. ESM only, one runtime dependency (fflate).",
5
5
  "keywords": [
6
6
  "xlsx",
@@ -10,7 +10,7 @@
10
10
  "spreadsheetml"
11
11
  ],
12
12
  "license": "MIT",
13
- "homepage": "https://github.com/DevXord/xlvane#readme",
13
+ "homepage": "https://xlvane.github.io/",
14
14
  "bugs": {
15
15
  "url": "https://github.com/DevXord/xlvane/issues"
16
16
  },