xlvane 1.2.3 → 1.3.1

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/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # xlvane 1.2.3: documentation in one file
1
+ # xlvane 1.3.1: 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
 
@@ -32,7 +32,7 @@ From a shell: `npx xlvane inspect orders.xlsx`, then `npx xlvane read orders.xls
32
32
  ## Contents
33
33
 
34
34
  - [Command line in 5 minutes](#command-line-in-5-minutes)
35
- - [Reading a workbook](#reading-a-workbook) · [Writing a workbook](#writing-a-workbook) (rich text, hyperlinks, notes, images, printing, [data validation and conditional formatting](#data-validation-and-conditional-formatting), [tables](#tables))
35
+ - [Reading a workbook](#reading-a-workbook) · [Writing a workbook](#writing-a-workbook) (rich text, hyperlinks, notes, images, [charts](#charts), printing, [data validation and conditional formatting](#data-validation-and-conditional-formatting), [tables](#tables))
36
36
  - [Tasks in one call](#tasks-in-one-call): tables of rows, CSV, JSON, editing files, diff · [inserting and deleting rows](#inserting-and-deleting-rows)
37
37
  - [Large files: streaming](#large-files-streaming)
38
38
  - [Inspecting a workbook](#inspecting-a-workbook) · [Errors and warnings](#errors-and-warnings) · [Untrusted input and limits](#untrusted-input-and-limits)
@@ -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.
@@ -178,10 +179,41 @@ for (const picture of read.getSheet('Report').images) console.log(picture.anchor
178
179
  ```
179
180
 
180
181
  - **Adding:** `workbook.addImage(bytes)` takes PNG, JPEG or GIF bytes (recognised from the first bytes; other formats throw `XLV_E_INVALID_IMAGE`) and returns a number; the same image placed many times is stored once. `sheet.addImage(image, anchor, { name?, description?, hyperlink? })` places it over a range (`"E1:G4"`), between two corners (`{ from, to, editAs }`, each a cell or a 0-based `{ col, row }` with fractions) or at a corner with a size in pixels (`{ from, size }`). `sheet.background(image)` sets the sheet background. Pixels are converted at 96 per inch (9525 EMU); fractional positions use the sheet's column widths and row heights.
181
- - **Reading:** `sheet.images` lists the pictures (`anchor` in EMU with the cell of each corner, `image` with `format`, `contentType` and `bytes`, `description`, `hyperlink`), `sheet.backgroundImage` the background, and `workbook.images` every image once. Charts, shapes and other drawing objects are not read; `XLV_W_DRAWING_CONTENT_NOT_READ` reports them, and `editCells` keeps them unchanged.
182
+ - **Reading:** `sheet.images` lists the pictures (`anchor` in EMU with the cell of each corner, `image` with `format`, `contentType` and `bytes`, `description`, `hyperlink`), `sheet.backgroundImage` the background, and `workbook.images` every image once. Charts are read into `sheet.charts` (see [Charts](#charts)); shapes and other drawing objects are not read: `XLV_W_DRAWING_CONTENT_NOT_READ` reports them, and `editCells` keeps them unchanged.
182
183
  - **Rows:** `insertRows` and `deleteRows` move pictures with their rows. A deletion that would cut a picture, and a drawing with charts or shapes, are refused with `XLV_E_NOT_SUPPORTED`.
183
184
  - **Streaming:** `writeWorkbookStream({ images: [png], sheets: [{ images: [{ image: 0, anchor: 'B2:D6' }], background: 0, rows }] })`.
184
185
 
186
+ ### Charts
187
+
188
+ ```ts
189
+ import { createWorkbook, readWorkbook, writeWorkbook } from 'xlvane';
190
+
191
+ const workbook = createWorkbook();
192
+ const sales = workbook.addSheet('Sales').set('A1', 'Month').set('B1', 'North').set('C1', 'South');
193
+ ['Jan', 'Feb', 'Mar'].forEach((month, i) => sales.set(`A${String(i + 2)}`, month).set(`B${String(i + 2)}`, 10 + i).set(`C${String(i + 2)}`, 20 - i));
194
+ sales.addChart({
195
+ type: 'column',
196
+ anchor: 'E2:L18',
197
+ title: 'Sales by month',
198
+ series: [
199
+ { name: 'Sales!B1', categories: 'Sales!A2:A4', values: 'Sales!B2:B4' },
200
+ { name: 'Sales!C1', categories: 'Sales!A2:A4', values: 'Sales!C2:C4', color: 'C00000' },
201
+ ],
202
+ legend: 'bottom',
203
+ axes: { y: { title: 'Units', min: 0 } },
204
+ });
205
+
206
+ const chart = readWorkbook(writeWorkbook(workbook)).getSheet('Sales').charts[0];
207
+ console.log(chart?.types, chart?.title, chart?.series.map((s) => s.values)); // ['column'] { text: 'Sales by month' } ['Sales!$B$2:$B$4', 'Sales!$C$2:$C$4']
208
+ ```
209
+
210
+ - **Adding:** `sheet.addChart({ type, anchor, series, title?, legend?, stacked?, axes?, name?, description? })` with `type` `column`, `bar`, `line`, `pie`, `scatter` or `area`; `anchor` as for pictures. Each series has `values` and optional `categories` (for scatter: the y and x values), each a sheet-qualified range of one row or one column such as `'Sales Data'!B2:B13`, an optional `name` (literal text, or a one-cell reference such as `Sales!B1`) and an optional `color` (`RRGGBB`). `stacked` is `true` or `'percent'` (not for pie and scatter); `legend` is `'right'` (default), `'bottom'`, `'top'`, `'left'` or `false`; `axes.x.title`, `axes.y.title`, `axes.y.min`, `axes.y.max`. A chart has 1 to 255 series; a pie exactly one, with at most 1 000 values. The sheets named in the ranges must exist when the workbook is written. Errors: `XLV_E_INVALID_CHART`.
211
+ - **What is written:** a chart part per chart with the series colours from a fixed palette (the accent colours of Excel's Office theme), since xlvane writes no theme part, and with the current cell values as caches, as Excel writes them. Combination charts, secondary axes, trendlines, data labels and chart sheets are not written.
212
+ - **Reading:** `sheet.charts` lists each chart's `name`, `description`, `anchor`, `types` (the chart groups in order, for example `['column', 'line']` for a combination chart; other chart types as `other:` and the element name), `stacked`, `title` (`{ text }`, `{ ref }` or `null`), `legend`, `axes` (`title`, `min`, `max`) and `series` (`type`, `name`, `categories`, `values` as reference text). References are never evaluated and cached values are not read. Charts on chart sheets are not read. `openWorkbookStream` does not report pictures or charts.
213
+ - **Streaming:** `writeWorkbookStream({ sheets: [{ name: 'Sales', rows, charts: [{ type: 'line', anchor: 'E2:L18', series: [...] }] }] })`. The streaming writer keeps no cell values, so its charts have no caches. LibreOffice draws such charts from the cells; whether Excel does is not verified.
214
+ - **Rows:** `insertRows` and `deleteRows` refuse sheets with charts, and workbooks whose charts may refer to the sheet, with `XLV_E_NOT_SUPPORTED`.
215
+ - **Checked with:** a copy of the ECMA-376 chart schema and LibreOffice 24.2.7.2, not with Excel. In the repository, `node scripts/chart-samples.mjs <directory>` (after `npm run build`) writes one sample file per type and variant.
216
+
185
217
  ### Sheet rules and printing
186
218
 
187
219
  ```ts
@@ -393,7 +425,7 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
393
425
  | `maxXmlNameLength` | 1 024 | huge element or attribute names |
394
426
  | `maxXmlTextLength` | 8 Mi characters | huge text nodes, attribute values, comments or processing instructions |
395
427
  | `maxXmlTreeElements` | 1 000 000 | huge workbook, styles or relationship parts (elements plus attributes) |
396
- | `maxCells` | 5 000 000 | workbooks too large to hold in memory (measured: about 180 bytes per numeric cell, Node.js 26); also bounds the output of `toCSV`, `readTable`, `toJSON` and `readTableStream` to `max(maxCells, 16 × cells read)` positions, and of `fromCSV`, `fromJSON` and `writeTable` to `max(maxCells, 16 × cells written)` |
428
+ | `maxCells` | 5 000 000 | workbooks too large to hold in memory (measured: about 180 bytes per numeric cell, Node.js 26); also bounds the output of `toCSV`, `readTable`, `toJSON` and `readTableStream` to `max(maxCells, 16 × cells read)` positions, and of `fromCSV`, `fromJSON` and `writeTable` to `max(maxCells, 16 × cells written)`; for `editCells` and `appendRows`, the cells of the workbook plus the new cells, and at most 1 000 000 cells written per call unless `maxCells` is given; for `insertRows` with `style`, the copied cell formats plus the cells of the workbook |
397
429
  | `maxSharedStringChars` | 512 Mi characters | many cells pointing at one huge shared string (a few MB that become many GB when values are copied); counted separately, the same for cells sharing one huge formula and for header names repeated in every row object of `readTable` and `toJSON` |
398
430
 
399
431
  ## Package size
@@ -404,7 +436,7 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
404
436
 
405
437
  - xlvane does not calculate formulas. It reads and writes formula text and the cached results stored in the file; edited files ask spreadsheet applications to recalculate when they are opened.
406
438
  - Password-protected (encrypted) workbooks and legacy `.xls` files are rejected with `XLV_E_UNSUPPORTED_CONTAINER`. A sheet protection password is not encryption and is supported.
407
- - Charts, shapes, pivot tables, slicers and form controls are not modelled: reading skips them (with a warning for drawings), `editCells` keeps them unchanged, and row operations refuse sheets where they would have to move.
439
+ - Shapes, pivot tables, slicers and form controls are not modelled: reading skips them (with a warning for drawings), `editCells` keeps them unchanged, and row operations refuse sheets where they would have to move. Charts are read and written for six types (see [Charts](#charts)); row operations refuse them too.
408
440
  - The command line reads files, not standard input.
409
441
 
410
442
  ## Links
@@ -530,7 +562,20 @@ const out = createWorkbook();
530
562
  out.addSheet('Report').addImage(out.addImage(png), 'B2:D6', { description: 'Logo' });
531
563
  ```
532
564
 
533
- Charts and shapes are not read (`XLV_W_DRAWING_CONTENT_NOT_READ`); `editCells` keeps them unchanged.
565
+ Charts (column, bar, line, pie, scatter, area), over sheet-qualified ranges of one row or column:
566
+
567
+ ```ts
568
+ import { createWorkbook, readWorkbook } from 'xlvane';
569
+
570
+ declare const bytes: Uint8Array;
571
+ const charts = readWorkbook(bytes).getSheet('Report').charts; // types, title, legend, axes, series refs (never evaluated)
572
+
573
+ const out = createWorkbook();
574
+ const sales = out.addSheet('Sales').set('A1', 'Month').set('B1', 'Sales').set('A2', 'Jan').set('B2', 10).set('A3', 'Feb').set('B3', 20);
575
+ sales.addChart({ type: 'column', anchor: 'D2:K16', title: 'Sales', series: [{ name: 'Sales!B1', categories: 'Sales!A2:A3', values: 'Sales!B2:B3' }] });
576
+ ```
577
+
578
+ Options: `legend` (`'right'` default, `'bottom'`, `'top'`, `'left'`, `false`), `stacked` (`true` or `'percent'`; column, bar, line, area), `axes: { x: { title }, y: { title, min, max } }`, per series `color: 'RRGGBB'`. For scatter, `categories` are the x values. A pie has one series. Errors: `XLV_E_INVALID_CHART`. Charts on chart sheets are not read; shapes are not read (`XLV_W_DRAWING_CONTENT_NOT_READ`); `editCells` keeps drawings unchanged.
534
579
 
535
580
  Printing and protection:
536
581
 
@@ -652,7 +697,7 @@ Each code links to its section, which says what to do. The CLI prints the code a
652
697
  | --- | --- |
653
698
  | [`XLV_E_INVALID_INPUT`](#xlv_e_invalid_input) | The input is not what the function expects: `readWorkbook` and `inspect` accept a `Uint8Array`, an `ArrayBuffer` (or another typed-array … |
654
699
  | [`XLV_E_INVALID_OPTION`](#xlv_e_invalid_option) | An option is unknown, has the wrong type or is out of range. |
655
- | [`XLV_E_INVALID_ARGUMENT`](#xlv_e_invalid_argument) | An argument of a method of `@xlvane/compat` is not valid, in a case where exceljs 4.4.0 accepts it and loses or moves data without an … |
700
+ | [`XLV_E_INVALID_ARGUMENT`](#xlv_e_invalid_argument) | An argument would lose or move data: a method of `@xlvane/compat` where exceljs 4.4.0 accepts it silently, or `appendRows` with array rows when the starting column is ambiguous. |
656
701
  | [`XLV_E_LIMIT_EXCEEDED`](#xlv_e_limit_exceeded) | The file exceeds one of the resource limits (`ReadLimits`): input size, number of ZIP entries, decompressed size of one entry or of all … |
657
702
  | [`XLV_E_NOT_ZIP`](#xlv_e_not_zip) | The input is not a ZIP archive, so it cannot be an XLSX file. |
658
703
  | [`XLV_E_UNSUPPORTED_CONTAINER`](#xlv_e_unsupported_container) | The input is a Compound File Binary container (signature `D0 CF 11 E0 A1 B1 1A E1`). |
@@ -675,6 +720,7 @@ Each code links to its section, which says what to do. The CLI prints the code a
675
720
  | [`XLV_E_INVALID_DEFINED_NAME`](#xlv_e_invalid_defined_name) | `defineName` (or `definedNames` of `writeWorkbookStream`) was given a name that spreadsheet applications do not accept, or a duplicate. |
676
721
  | [`XLV_E_INVALID_HYPERLINK`](#xlv_e_invalid_hyperlink) | `link` (or `hyperlinks` of a sheet of `writeWorkbookStream`) was given a hyperlink that cannot be stored: not an object, an unknown … |
677
722
  | [`XLV_E_INVALID_IMAGE`](#xlv_e_invalid_image) | `NewWorkbook.addImage` (or `images` of `writeWorkbookStream`) was given something that is not the bytes of a PNG, JPEG or GIF file. |
723
+ | [`XLV_E_INVALID_CHART`](#xlv_e_invalid_chart) | `addChart` (or `charts` of a sheet of `writeWorkbookStream`) was given a chart xlvane cannot write: an unknown type or option, no series or more than 255, a range that is not one row or column of a named sheet, … |
678
724
  | [`XLV_E_INVALID_TABLE`](#xlv_e_invalid_table) | `addTable` (or `tables` of a sheet of `writeWorkbookStream`) was given a table that spreadsheet applications do not accept: a name or … |
679
725
  | [`XLV_E_OVERLAPPING_MERGE`](#xlv_e_overlapping_merge) | A range passed to `merge()` shares cells with a range merged before. |
680
726
  | [`XLV_E_FILE_EXISTS`](#xlv_e_file_exists) | `writeWorkbookFile` was asked to write to a path where a file already exists, and `overwrite` was not set. |
@@ -688,6 +734,8 @@ Each code links to its section, which says what to do. The CLI prints the code a
688
734
  | [`XLV_E_NOT_SUPPORTED`](#xlv_e_not_supported) | `@xlvane/compat` was asked for an exceljs feature it does not implement, for example a rich-text or hyperlink value, images, tables, … |
689
735
  | [`XLV_E_USAGE`](#xlv_e_usage) | The `xlvane` command line was used incorrectly: an unknown command or option, a missing or extra argument, an option without its value, … |
690
736
  | [`XLV_E_INTERNAL`](#xlv_e_internal) | The `xlvane` command line caught an error that is not an `XlvError`. |
737
+ | [`XLV_E_NOT_INSTALLED`](#xlv_e_not_installed) | `xlvane mcp` needs the separate package `@xlvane/mcp`, which could not be loaded. |
738
+ | [`XLV_E_PATH_NOT_ALLOWED`](#xlv_e_path_not_allowed) | The MCP server was asked to read or write a path outside the directories it was started with (`--root`), or to write through a symbolic link. |
691
739
 
692
740
  | Warning | What it means |
693
741
  | --- | --- |
@@ -702,13 +750,14 @@ Each code links to its section, which says what to do. The CLI prints the code a
702
750
  | [`XLV_W_LIMIT_REACHED`](#xlv_w_limit_reached) | Reading stopped computing something derived from the file because the work would be far out of proportion to its data. |
703
751
  | [`XLV_W_INVALID_VALUE`](#xlv_w_invalid_value) | 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 … |
704
752
  | [`XLV_W_UNUSUAL_HYPERLINK`](#xlv_w_unusual_hyperlink) | A sheet has hyperlinks whose external target uses a scheme other than `http`, `https` or `mailto`, for example `file:`, `javascript:` or … |
705
- | [`XLV_W_DRAWING_CONTENT_NOT_READ`](#xlv_w_drawing_content_not_read) | The drawing of a sheet holds objects other than pictures: charts, shapes, connectors, group shapes, content parts or alternate content. |
753
+ | [`XLV_W_DRAWING_CONTENT_NOT_READ`](#xlv_w_drawing_content_not_read) | The drawing of a sheet holds objects other than pictures and charts: shapes, connectors, group shapes, other graphic frames, content parts or alternate content. |
706
754
  | [`XLV_W_THREADED_COMMENTS`](#xlv_w_threaded_comments) | A sheet has threaded comments, the conversations of current Excel versions ([MS-XLSX] §2.3.7). |
707
755
  | [`XLV_W_SHEET_PART_MISSING`](#xlv_w_sheet_part_missing) | A sheet listed in the workbook has no part (the relationship or the part itself is missing). |
708
756
  | [`XLV_W_SHEET_PART_SHARED`](#xlv_w_sheet_part_shared) | Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML or table part. |
709
757
  | [`XLV_W_PART_MISSING`](#xlv_w_part_missing) | The workbook points to a styles or shared strings part that does not exist. |
710
758
  | [`XLV_W_FORMULAS_NOT_RECALCULATED`](#xlv_w_formulas_not_recalculated) | Reported by `editCells` and `appendRows` when the workbook contains formulas. |
711
759
  | [`XLV_W_INVALID_CELL_REF`](#xlv_w_invalid_cell_ref) | A worksheet contains a row whose number is not a whole number from 1 to 1048576, a cell whose `r` reference is not a valid A1 reference … |
760
+ | [`XLV_W_MERGED_CELL_WRITE`](#xlv_w_merged_cell_write) | `editCells` or `appendRows` wrote a value into a cell of a merged range other than its top-left cell, where it is not shown. |
712
761
 
713
762
  ## Errors
714
763
 
@@ -724,6 +773,7 @@ The settings of a note box (`box` of a note: `margins` in millimetres from 0 to
724
773
 
725
774
  ### XLV_E_INVALID_ARGUMENT
726
775
  An argument of a method of `@xlvane/compat` is not valid, in a case where exceljs 4.4.0 accepts it and loses or moves data without an error: for example `insertRow(0, …)`, a row position given as a string, a negative count in `spliceRows` or `spliceColumns`, an outline level outside 0–7 set on a row or column (exceljs writes it), `duplicateRow` of a row that does not exist, `insertRows` without an array of values, or rows or columns pushed beyond the sheet (row 1048576, column XFD). `details.method`, `details.argument` and `details.value` name the method, the argument and its value (docs/exceljs-compat.md).
776
+ `appendRows` throws it for array rows when tables that start in different columns reach the sheet's last used row, so the column where the rows start is ambiguous (D-K04); `details.tables` names the tables and `details.row` the row. Pass object rows keyed by header text.
727
777
  **Fix:** pass a whole number in the range the hint gives (rows from 1, columns from 1), or remove rows at the end of the sheet before inserting.
728
778
 
729
779
  ### XLV_E_LIMIT_EXCEEDED
@@ -739,6 +789,14 @@ From 1.2.3 (D-J01):
739
789
 
740
790
  - `maxCells`: `fromCSV`, `fromJSON` and `writeTable` write at most `max(maxCells, 16 × cells written)` positions (rows × columns), so one very wide row followed by many short ones cannot make them visit every position; each takes a `maxCells` option (default 5 000 000). While streaming, one `<row>` element may hold at most 16 384 cells (`details.value` 16384), the most a row can have, because its cells are held until the element ends. `@xlvane/compat` `xlsx.load` also counts the cells of defined names against its budget, and its `xlsx.read`, `csv.read` and `stream.xlsx.WorkbookReader` stop collecting a stream after `maxInputBytes` (256 MiB; `details.limit` `maxInputBytes`).
741
791
 
792
+ From 1.3.0 (D-K04):
793
+
794
+ - `maxCells`: `editCells` and `appendRows` check, before building any cell, that the cells of the workbook (with a value or only a style) plus the new cells stay within `maxCells`, so the result can be read with the same limits, and they write at most 1 000 000 cells per call unless `limits.maxCells` is given, then at most `maxCells` (`details.limit` `maxCells`, `details.value` the limit). Each cell written takes about 0.5 KB of memory while the file is rebuilt; split the edits into several calls, or pass a larger `maxCells` if there is enough memory.
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`.
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.
797
+
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.
799
+
742
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.
743
801
 
744
802
  `fromCSV` (and `xlvane convert`/`create` with CSV input) throws it for text with more fields than `maxCells` (default 5 000 000, `details.limit` `maxCells`), more than 16 384 fields on a line or more than 1 048 576 lines.
@@ -863,6 +921,10 @@ The workbook already has a sheet with this name. Names are compared ignoring upp
863
921
  `NewWorkbook.addImage` (or `images` of `writeWorkbookStream`) was given something that is not the bytes of a PNG, JPEG or GIF file. The format is recognised from the first bytes of the file (PNG `89 50 4E 47 0D 0A 1A 0A`, JPEG `FF D8 FF`, GIF `GIF87a` or `GIF89a`), not from a file name. `details.size` is the number of bytes given.
864
922
  **Fix:** pass the file contents as a `Uint8Array`, for example `new Uint8Array(await file.arrayBuffer())`, and convert other formats (SVG, EMF, WMF, BMP, TIFF, WebP) to PNG or JPEG first.
865
923
 
924
+ ### XLV_E_INVALID_CHART
925
+ `addChart` (or `charts` of a sheet of `writeWorkbookStream`) was given a chart xlvane cannot write: a type other than `column`, `bar`, `line`, `pie`, `scatter` or `area`; an unknown option; no series or more than 255 (a limit of xlvane); `values` or `categories` that are not a sheet-qualified range of one row or one column such as `'Sales'!B2:B13` (no other workbook, no 3-D or whole-column references, no lists); a series `name` that is a reference to more than one cell, or literal text that is empty, longer than 255 characters or holds control characters (also line breaks); a `color` that is not six hex digits `RRGGBB`; a title or axis title that is empty, longer than 255 characters or holds control characters; a `legend` other than `right`, `bottom`, `top`, `left` or `false`; `stacked` on a pie or scatter chart; a pie chart with more than one series, a `color`, `axes` or more than 1 000 values; an axis `min` or `max` that is not a finite number, or `min` not below `max`; a `name` or `description` as refused for pictures; a `legend` or `stacked` of `null` (leave the option out for the default); a hole or a value other than an object in `series`; an invalid position (as for `addImage`). When the workbook is written (for the streaming writer: when every sheet is known), a range that names a sheet the workbook does not have also gives this error, and so does a sheet name given without quotes that reads as a cell or R1C1 reference (such as `S1!$A$1`) when the workbook has no sheet of exactly that name. `writeWorkbook` also refuses charts whose cached values would cover more than 4 000 000 cells, or more than 8 388 608 characters of cached text after escaping, in the whole workbook (limits of xlvane, `details.limit`; `writeWorkbookStream` writes no cached values). `details.option` names the option; `location.sheet` is the sheet of the chart.
926
+ **Fix:** give each range with its sheet, for example `values: "'Sales'!B2:B13"` and `categories: "'Sales'!A2:A13"`, use one row or one column per range, quote sheet names such as `'S1'`, and add the sheet before writing the workbook. For charts over millions of cells, refer to fewer cells or use `writeWorkbookStream`.
927
+
866
928
  ### XLV_E_INVALID_TABLE
867
929
  `addTable` (or `tables` of a sheet of `writeWorkbookStream`) was given a table that spreadsheet applications do not accept: a name or display name that is not a valid name (letters, digits, `_`, `.` and `\`, starting with a letter or `_`; no spaces; not a cell reference such as `A1` or `R1C1`; at most 255 characters; ECMA-376 Part 1 §18.5.1.2, §18.17.2.5, [MS-OI29500] §2.1.680), a name already used by another table or a defined name of the workbook (ignoring case), no columns or two columns with the same name (ignoring case), a column name longer than 255 characters, a totals label together with a totals function, `totalsRowFormula` without `totalsRowFunction: "custom"` or the reverse, a style that is not one of the 60 built-in table styles, a range that is not as wide as the columns, leaves no data row or reaches beyond the sheet, a range that overlaps another table, a merged range or the sheet filter ([MS-OI29500] §2.1.680 h; `autoFilter: true` of the streaming writer covers every used cell, so it cannot be combined with tables), or a name equal to a built-in name such as `Print_Area`, `Print_Titles`, `Criteria` or `Database` (with or without `_xlnm.`) or starting with `_xl`. `merge` also throws it for a range that overlaps a table, and so does `defineName` (as `XLV_E_INVALID_DEFINED_NAME`) for a table's name. `details.name` is the table name.
868
930
  **Fix:** choose another name, for example `Sales_2026` instead of `Sales 2026`, give every column a unique name, or move the table so that it does not touch other tables or merged cells.
@@ -908,7 +970,7 @@ The CSV text given to `fromCSV` has a quoted field that is never closed, text ri
908
970
  `hashPassword` and `verifyPassword` throw it for a hash algorithm other than SHA-1, SHA-256, SHA-384 and SHA-512, and `hashPassword` without a `salt` throws it in a runtime that has no `crypto.getRandomValues` (pass your own random salt there; SR-50, D-C08).
909
971
  `editCells` throws it for an array or shared formula in an edit (it writes plain formulas only), and for a cell inside an array formula's range or the master of a shared formula with other cells in its range (D-E02); `details.cell` names it.
910
972
  `appendRows` throws it when a table ends with a totals row on the sheet's last used row (the new rows would fall below the totals row; the hint gives the `insertRows` call that grows the table instead), or when growing a table for the new rows would overlap another table (D-R03).
911
- `insertRows` and `deleteRows` (and `xlvane edit --insert-rows` / `--delete-rows`) also throw it when the sheet has content whose cell references xlvane cannot move yet: tables, charts, shapes and other drawing objects (pictures alone are moved, and deleting rows that would cut a picture is refused), pivot tables, form controls, embedded objects, custom sheet views, threaded comments, extended content such as sparklines or slicers (the conditional formats and data validations of the [MS-XLSX] extension are moved, D-F04), a chart or pivot cache elsewhere that names the sheet, a data table, an array formula the change would split, or a formula or defined name that cannot be adjusted safely (for example a 3-D reference such as `Jan:Mar!A1` that includes the sheet, D-B01). The message names the part or the cell, and `details` gives the element, relationship type or formula.
973
+ `insertRows` and `deleteRows` (and `xlvane edit --insert-rows` / `--delete-rows`) also throw it when the sheet has content whose cell references xlvane cannot move yet: tables, charts, shapes and other drawing objects (pictures alone are moved, and deleting rows that would cut a picture is refused), pivot tables, form controls, embedded objects, custom sheet views, threaded comments, extended content such as sparklines or slicers (the conditional formats and data validations of the [MS-XLSX] extension are moved, D-F04), a chart or pivot cache elsewhere that names the sheet (also when the XML escapes the name, such as `R&amp;D`, or quotes it, such as `'O''Brien'!`; pivot cache definitions are found by part name, content type or workbook relationship, D-K04), a drawing, chart or pivot cache relationship whose target is not a part of the package (for example a chart stored under a percent-encoded name; `details.type` and `details.target`), a data table, an array formula the change would split, or a formula or defined name that cannot be adjusted safely (for example a 3-D reference such as `Jan:Mar!A1` that includes the sheet, D-B01). The message names the part or the cell, and `details` gives the element, relationship type or formula.
912
974
  **Fix:** for `@xlvane/compat`, see docs/exceljs-compat.md for the supported subset; remove the call, or keep using exceljs for files that need the feature.
913
975
  For `insertRows`/`deleteRows`, insert or delete the rows in a spreadsheet application, or remove the named content first.
914
976
 
@@ -925,7 +987,7 @@ The `xlvane` command line caught an error that is not an `XlvError`. This is a b
925
987
  **Fix:** report it with the command and, if possible, the input file (see SECURITY.md for files that must stay private).
926
988
 
927
989
  ### XLV_E_PATH_NOT_ALLOWED
928
- The MCP server (`@xlvane/mcp`) was asked to read or write a path outside the directories it was started with (`--root`), including paths that leave them through `..` or a symbolic link, or to write through a symbolic link. Nothing was read or written. `details.path` is the path as given and `details.roots` lists the allowed directories.
990
+ The MCP server (`@xlvane/mcp`) was asked to read or write a path outside the directories it was started with (`--root`), including paths that leave them through `..` or a symbolic link (also a link that leaves them and comes back), or to write through a symbolic link. Nothing was read or written. `details.path` is the path as given and `details.roots` lists the allowed directories.
929
991
  **Fix:** use a path inside one of the listed directories, or restart the server with another `--root`.
930
992
 
931
993
  ## Warnings
@@ -951,8 +1013,8 @@ Some merged ranges have values in cells other than the top-left one. Spreadsheet
951
1013
  **Fix:** write the value into the top-left cell of the range (named in the message), or unmerge the range first.
952
1014
 
953
1015
  ### XLV_W_LIMIT_REACHED
954
- Reading stopped computing something derived from the file because the work would be far out of proportion to its data, and the file is read without it. Today: hyperlinks whose ranges cover far more positions than the sheet has cells (more than 16 visits per cell plus 1 000 000); `sheet.hyperlinks` lists every link, but `cell.hyperlink` is not set for the cells of the remaining links (D-H01).
955
- **Fix:** nothing, if the file comes from an unknown source: this protects the reader. Look up a cell's link in `sheet.hyperlinks` by its range.
1016
+ Reading stopped computing something derived from the file because the work would be far out of proportion to its data, and the file is read without it. Today: hyperlinks whose ranges cover far more positions than the sheet has cells (more than 16 visits per cell plus 1 000 000); `sheet.hyperlinks` lists every link, but `cell.hyperlink` is not set for the cells of the remaining links (D-H01). A chart part whose XML is over a limit that applies to one part (`maxXmlTreeElements`, `maxXmlDepth`, `maxXmlAttributes`, `maxXmlNameLength` or `maxXmlTextLength`; the message names it) is skipped: the chart is not in `sheet.charts`, and the rest of the workbook is read (D-K01). The cached and literal values of a chart are parsed under these limits but do not count against `maxXmlTreeElements`. Charts are read after the cells of every sheet, and their chart groups and series count against what the cells leave of `maxCells`; beyond it, that chart and the chart parts read after it are skipped without being parsed.
1017
+ **Fix:** nothing, if the file comes from an unknown source: this protects the reader. Look up a cell's link in `sheet.hyperlinks` by its range. To read a skipped chart of a trusted file, raise the limit the message names, for example `{ limits: { maxXmlTreeElements: 4000000 } }` or `{ limits: { maxCells: 20000000 } }`.
956
1018
 
957
1019
  ### XLV_W_VERY_HIDDEN_SHEET
958
1020
  A sheet has the state `veryHidden`: spreadsheet applications offer no menu command to unhide it, so users usually do not know it exists.
@@ -971,7 +1033,7 @@ A sheet defines the same cell more than once. The last definition is used, also
971
1033
  **Fix:** nothing, if the last definition is right; otherwise re-save the file in the application that made it.
972
1034
 
973
1035
  ### XLV_W_INVALID_VALUE
974
- 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.
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).
975
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.
976
1038
 
977
1039
  ### XLV_W_UNUSUAL_HYPERLINK
@@ -979,8 +1041,8 @@ A sheet has hyperlinks whose external target uses a scheme other than `http`, `h
979
1041
  **Fix:** nothing for reading; do not open such targets without checking them.
980
1042
 
981
1043
  ### XLV_W_DRAWING_CONTENT_NOT_READ
982
- The drawing of a sheet holds objects other than pictures: charts, shapes, connectors, group shapes, content parts or alternate content. xlvane reads only the pictures (`Sheet.images`); the message gives the number of other objects. `editCells` keeps the whole drawing unchanged; `insertRows` and `deleteRows` refuse such sheets.
983
- **Fix:** nothing, if you need only the pictures; edit sheets with charts or shapes in a spreadsheet application.
1044
+ The drawing of a sheet holds objects other than pictures and charts: shapes, connectors, group shapes, other graphic frames (for example diagrams), content parts or alternate content. xlvane reads only the pictures (`Sheet.images`) and charts (`Sheet.charts`); the message gives the number of other objects. `editCells` keeps the whole drawing unchanged; `insertRows` and `deleteRows` refuse such sheets, and also sheets with charts.
1045
+ **Fix:** nothing, if you need only the pictures and charts; edit sheets with shapes in a spreadsheet application.
984
1046
 
985
1047
  ### XLV_W_THREADED_COMMENTS
986
1048
  A sheet has threaded comments, the conversations of current Excel versions ([MS-XLSX] §2.3.7). xlvane reads only the notes (legacy comments, ECMA-376 Part 1 §18.7). Excel stores a placeholder note next to each conversation; its author is `tc={id}` and its text may not contain the replies. `Sheet.notes` lists these placeholders as they are stored.
@@ -991,7 +1053,7 @@ A sheet listed in the workbook has no part (the relationship or the part itself
991
1053
  **Fix:** re-save the file in the application that made it; the sheet's cells are not in the file.
992
1054
 
993
1055
  ### XLV_W_SHEET_PART_SHARED
994
- Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML or table part (part names compared without regard to case, as OPC requires). Spreadsheet applications write one such part per sheet; reading a shared part again for every sheet would decompress and parse it many times, outside the limit on decompressed bytes. The part is read for the first of these sheets only: for a worksheet part, the others are returned with `part: null` and no cells; for a comments, VML or table part, the others are read without its notes or table. A sheet that relates to one table part twice gets that table once. The location names the sheet and the part.
1056
+ Two or more sheets listed in the workbook lead to the same worksheet part, or relate to the same comments, VML, table or drawing part (part names compared without regard to case, as OPC requires). Spreadsheet applications write one such part per sheet; reading a shared part again for every sheet would decompress and parse it many times, outside the limit on decompressed bytes. The part is read for the first of these sheets only: for a worksheet part, the others are returned with `part: null` and no cells; for a comments, VML or table part, the others are read without its notes or table; for a drawing part, which belongs to the first worksheet whose `<drawing>` shows it, the others are read without its pictures and charts. A sheet that relates to one table part twice gets that table once. The location names the sheet and the part.
995
1057
  **Fix:** re-save the file in the application that made it, and check whether the sheets returned without cells should hold data.
996
1058
 
997
1059
  ### XLV_W_PART_MISSING
@@ -1067,6 +1129,8 @@ A worksheet contains a row whose number is not a whole number from 1 to 1048576,
1067
1129
  | `noteCount` | number | notes (comments) of the sheet (`Sheet.notes`) |
1068
1130
  | `imageCount` | number | pictures of the sheet's drawing (`Sheet.images`); charts and shapes are not counted |
1069
1131
  | `images` | array | up to 10 pictures: `cell` of the top-left corner (`null` for an absolute position) and `format` (`png`, `jpeg`, `gif`, or `null` for other formats) |
1132
+ | `chartCount` | number | charts of the sheet's drawing (`Sheet.charts`); charts on chart sheets are not counted |
1133
+ | `charts` | array | up to 10 charts: `cell` of the top-left corner (`null` for an absolute position), `types` (the first 10 chart groups in order: `column`, `bar`, `line`, `pie`, `scatter`, `area`, or `other:` and the element name, e.g. `other:radarChart`), `typesTruncated` (`true` when the chart has more than 10 chart groups), `title` (`{ "text" }`, `{ "ref" }` with the reference as stored, either cut to 100 characters followed by `…`, or `null`) and `seriesCount` |
1070
1134
  | `notes` | string[] | up to ten cells that have a note |
1071
1135
  | `protected` | boolean | the sheet is protected (`Sheet.protection.sheet`) |
1072
1136
  | `printArea` | string[] \| null | the print area (`Sheet.printArea`), at most ten ranges |
@@ -1142,6 +1206,8 @@ Values in `samples` are JSON-safe: numbers, booleans, strings (cut to 100 charac
1142
1206
  "noteCount": 0,
1143
1207
  "imageCount": 0,
1144
1208
  "images": [],
1209
+ "chartCount": 0,
1210
+ "charts": [],
1145
1211
  "notes": [],
1146
1212
  "protected": false,
1147
1213
  "printArea": null,
@@ -1255,7 +1321,7 @@ npm installs an alias `<alias>@npm:<name>` under the alias name in `node_modules
1255
1321
 
1256
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.
1257
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`.
1258
- - **Unsupported API** throws an `XlvError` with code `XLV_E_NOT_SUPPORTED`, naming the method (docs/errors.md). Nothing is dropped silently.
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.
1259
1325
 
1260
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`).
1261
1327
 
@@ -1273,15 +1339,15 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1273
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` |
1274
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` |
1275
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 |
1276
- | `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`. | `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 |
1277
1343
  | `xlsx.readFile(path)` | supported | | `lib/xlsx/xlsx.js` L49 |
1278
1344
  | `xlsx.read(stream)` | supported | | `lib/xlsx/xlsx.js` L244 |
1279
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 |
1280
1346
  | `xlsx.writeFile(path)` | supported | | `lib/xlsx/xlsx.js` L666 |
1281
1347
  | `xlsx.write(stream)` | supported | | `lib/xlsx/xlsx.js` L643 |
1282
1348
  | `xlsx.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/xlsx/xlsx.js` L238 |
1283
- | `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`) 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 (exceljs keeps it in the first field), `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`. | `lib/csv/csv.js` L32–98 |
1284
- | `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); 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. | `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 |
1285
1351
  | `csv.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/csv/csv.js` L101 |
1286
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 |
1287
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) |
@@ -1299,12 +1365,12 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1299
1365
  | `eachRow({ includeEmpty }, cb)` | supported | | `lib/doc/worksheet.js` L551 |
1300
1366
  | `getSheetValues()` | supported | Every row object, also an empty one (`[]`), as in exceljs. | `lib/doc/worksheet.js` L571–579 |
1301
1367
  | `rowCount`, `actualRowCount`, `columnCount` | supported | | `lib/doc/worksheet.js` L374, L378, L313 |
1302
- | `mergeCells(range \| t, l, b, r)`, `unMergeCells` | supported | | `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 |
1303
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 |
1304
- | `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 |
1305
1371
  | `autoFilter` (range string) | supported | | `lib/doc/worksheet.js` L116 |
1306
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 |
1307
- | `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 |
1308
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 |
1309
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 |
1310
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 |
@@ -1325,7 +1391,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1325
1391
  | `eachCell({ includeEmpty }, cb)` | supported | As in exceljs, cells of a merged range give the master cell's value. | `lib/doc/row.js` L130 |
1326
1392
  | `height`, `hidden` | supported | | `lib/doc/row.js` L310 |
1327
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 |
1328
- | `font`, `fill`, `border`, `alignment`, `numFmt` setters | supported | Apply to the row's cells. | `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 |
1329
1395
  | `commit()` | supported | Meaningful for `WorkbookWriter`; does nothing in a normal workbook, as in exceljs. | `lib/doc/row.js` L29 |
1330
1396
  | `cellCount`, `actualCellCount` | supported | | `lib/doc/row.js` L216, L220 |
1331
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 |
@@ -1339,17 +1405,17 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1339
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 |
1340
1406
  | `letter`, `number` | supported | | `lib/doc/column.js` L23, L31 |
1341
1407
  | `values` | supported | | `lib/doc/column.js` L179 |
1342
- | `eachCell({ includeEmpty }, cb)` | supported | | `lib/doc/column.js` L168 |
1343
- | `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 |
1344
1410
 
1345
1411
  ## Cell values
1346
1412
 
1347
1413
  | Value | Status | Notes | Source in exceljs |
1348
1414
  |---|---|---|---|
1349
1415
  | `null`, number, string, boolean | supported | | `lib/doc/cell.js` L206, L211 |
1350
- | `Date` | supported | 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"`. | `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 |
1351
1417
  | `{ formula, result }` | supported | xlvane does not calculate; `result` is stored as given. | `lib/doc/cell.js` L734 |
1352
- | `{ 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. `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. 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 |
1353
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` |
1354
1420
  | `{ error: '#N/A' }` | supported | | `lib/doc/cell.js` L970 |
1355
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 |
@@ -1367,12 +1433,12 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1367
1433
  | API | Status | Notes | Source in exceljs |
1368
1434
  |---|---|---|---|
1369
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 |
1370
- | `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 |
1371
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 |
1372
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 |
1373
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 |
1374
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` |
1375
- | Colours `{ argb }`, `{ theme, tint }`, `{ indexed }` | supported | | `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` |
1376
1442
 
1377
1443
  ## Streaming
1378
1444
 
@@ -1384,7 +1450,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1384
1450
  | Sheet layout (`columns`, `views`, `properties.tabColor`, `autoFilter`, merges, data validations, conditional formatting, protection, `pageSetup`, `headerFooter`, row breaks) of a streamed worksheet | partly | Must be set before the worksheet's first row is committed. A change made later throws `XLV_E_NOT_SUPPORTED` (at `mergeCells()` or at `worksheet.commit()`), because xlvane writes the layout before the rows. exceljs writes `autoFilter`, merges, validations, conditional formatting and print settings when the worksheet is committed. | `lib/stream/xlsx/worksheet-writer.js` |
1385
1451
  | `WorkbookWriter` options `created`, `modified`, `creator`, `lastModifiedBy`, `lastPrinted`; `definedNames` | partly | Written with exceljs's defaults (`creator` and `lastModifiedBy` "ExcelJS"). Names and these properties are written when the first row is committed (writing starts then); a change made later throws `XLV_E_NOT_SUPPORTED` at `writer.commit()`. exceljs writes them at the end. | `lib/stream/xlsx/workbook-writer.js` L22–40, L64, L264 |
1386
1452
  | `stream.xlsx.WorkbookReader` with `for await` over worksheets and rows | supported | Built on `openWorkbookStream`. Rich text cells come back as `{ richText }`. | `lib/stream/xlsx/workbook-reader.js` L19, L71; `lib/stream/xlsx/worksheet-reader.js` L105 |
1387
- | `WorkbookReader` options and events (`read()`, `parse()`, `on('worksheet' \| 'entry' \| 'end' \| 'finished' \| 'error')`; worksheet `read()`, `on('row' \| 'finished')`) | partly | Options are checked against exceljs's `WorkbookReader.Options` (X4-26). `styles: 'cache'` gives cells their styles; `entries: 'emit'` sends `entry` events for the workbook and each worksheet (not for shared strings, styles or hyperlinks); `hyperlinks: 'cache'` is accepted and, as in exceljs for files in schema order, attaches nothing. `sharedStrings: 'emit' \| 'ignore'` and `hyperlinks: 'emit'` throw `XLV_E_NOT_SUPPORTED`. Without `styles: 'cache'` a date is still read as a `Date` (exceljs gives the serial number, because it does not know the format). The worksheet reader has `name`, `id`, `state`, `workbook`, `dimensions`, `columns`, `getColumn`, the column-key methods and `destroy` (throws). The events come from a small emitter in the package, not from `node:events`. | `lib/stream/xlsx/workbook-reader.js` L20–66, L151, L326; `lib/stream/xlsx/worksheet-reader.js` L13–115 |
1453
+ | `WorkbookReader` options and events (`read()`, `parse()`, `on('worksheet' \| 'entry' \| 'end' \| 'finished' \| 'error')`; worksheet `read()`, `on('row' \| 'finished')`) | partly | Options are checked against exceljs's `WorkbookReader.Options` (X4-26). `styles: 'cache'` gives cells their styles; `entries: 'emit'` sends `entry` events for the workbook and each worksheet (not for shared strings, styles or hyperlinks); `hyperlinks: 'cache'` is accepted and, as in exceljs for files in schema order, attaches nothing. `sharedStrings: 'emit' \| 'ignore'` and `hyperlinks: 'emit'` throw `XLV_E_NOT_SUPPORTED`. Without `styles: 'cache'` a date is still read as a `Date` (exceljs gives the serial number, because it does not know the format). A shared formula's master is read as `{ formula, result }`, as exceljs's reader gives it, and each dependent as `{ sharedFormula, result }` whose `cell.formula` is the master's formula moved to it (exceljs's reader gives `{ formula: '', result }`, which exceljs takes as a JSON value; K2), built once per cell and counted against the same limit of moved shared-formula text as in `xlsx.load` (see `{ sharedFormula, result }`), per read of the workbook (K4 S1); a dependent the formula cannot be moved to gives its cached value. The worksheet reader has `name`, `id`, `state`, `workbook`, `dimensions`, `columns`, `getColumn`, the column-key methods and `destroy` (throws). The events come from a small emitter in the package, not from `node:events`. | `lib/stream/xlsx/workbook-reader.js` L20–66, L151, L326; `lib/stream/xlsx/worksheet-reader.js` L13–115 |
1388
1454
 
1389
1455
  ## Other
1390
1456
 
@@ -1411,6 +1477,7 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
1411
1477
  - Row heights above 409.5 points are read as 409.5; 409.5 itself, which Excel writes, is accepted.
1412
1478
  - A table whose style is a custom style of the file is written without a style name, because xlvane does not write the file's custom table styles; a style name set in code must still be one of the built-in names.
1413
1479
  - Totals-row formulas keep their cached results.
1480
+ - Charts are not kept. exceljs 4.4.0 has no chart API (`lib/` mentions charts only in a comment of `lib/xlsx/xform/book/workbook-xform.js` L174–178; docs/facts/charts.md CH-30) and drops the charts of a file it reads and writes again; `@xlvane/compat` does the same and offers no chart API. xlvane itself writes charts with `NewSheet.addChart` and reads them as `Sheet.charts`.
1414
1481
  - `activeTab` and `firstSheet` count chart sheets in the file; chart sheets are not loaded, so the indexes are mapped to the worksheet they fall on, or the worksheet before a chart sheet.
1415
1482
  - Row height, visibility and outline level are also read for rows without cells, up to the sheet's `dimension` or its last cell; rows after both are not looked at.
1416
1483
 
@@ -1422,7 +1489,7 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
1422
1489
  - `row.destroy()` and `cell.destroy()` do nothing; the row or cell stays usable (exceljs deletes its internals).
1423
1490
  - Errors found while writing name the worksheet and, for cell values and styles, the cell (`error.location`).
1424
1491
  - `worksheet.tables` and `dataValidations.model` have no prototype: keys such as `constructor` or `__proto__` are plain entries.
1425
- - CSV writing: a field whose first character after any NUL characters is a formula character is escaped (NULs are removed when writing); options that would get around the escape are refused (CSV-13); `latin1` output writes characters above U+00FF as `?`; `csv.read` stops collecting input after 256 MiB (`XLV_E_LIMIT_EXCEEDED`, `maxInputBytes`); with `formatterOptions.quote: false`, a field with CR, LF or the delimiter throws `XLV_E_INVALID_OPTION` (fast-csv writes it and the CSV then has other fields or rows). CSV reading: fields such as `constructor` or `__proto__` stay text.
1492
+ - CSV writing: a field whose first character after any NUL characters is a formula character is escaped (NULs are removed when writing); options that would get around the escape are refused (CSV-13); `latin1` output writes characters above U+00FF as `?`; a leading byte order mark, a BOM or UTF-8 formula sign in the bytes of `latin1` or `utf16le` output, values from `map` that are not strings, and a comma, `;`, tab or `|` inside a field are handled as described for `csv.write` (K2); `csv.read` stops collecting input after 256 MiB (`XLV_E_LIMIT_EXCEEDED`, `maxInputBytes`) and parsing at 1 048 576 rows or 16 384 fields in a row; with `formatterOptions.quote: false`, a field with CR, LF or the delimiter throws `XLV_E_INVALID_OPTION` (fast-csv writes it and the CSV then has other fields or rows). CSV reading: fields such as `constructor` or `__proto__` stay text. `xlsx.load` limits merged cells to max(1 048 576, cells in the file), and does not build the text of shared-formula dependents (K2). After K3: `csv.read` adds at most 5 000 000 fields; with formulas escaped, an empty delimiter, non-ASCII delimiters with `latin1` or `utf16le`, and `utf16le` fields that LibreOffice's 8-bit reading would split before `=` are refused, and `utf16le` output starts with its byte order mark (see `csv.write`); dependents from the streaming reader carry their formula for the streaming writer; notes in a merged range keep the block writing of its style.
1426
1493
 
1427
1494
  ## Known differences found by the cross-check
1428
1495
 
@@ -1430,6 +1497,238 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
1430
1497
 
1431
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`).
1432
1499
 
1500
+ ## Known differences from the 1.3.0 review
1501
+
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.
1503
+
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.
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.
1506
+
1507
+ <!-- file: docs/migrate-from-exceljs.md -->
1508
+
1509
+ # Migrating from ExcelJS
1510
+
1511
+ xlvane is not affiliated with the ExcelJS project; its name is used only to identify the package.
1512
+
1513
+ `@xlvane/compat` offers the commonly used part of the exceljs 4.4.0 API on top of xlvane. It is installed under the name `exceljs` with an npm alias, so code that imports `exceljs` keeps its imports:
1514
+
1515
+ ```json
1516
+ "dependencies": {
1517
+ "exceljs": "npm:@xlvane/compat@^1"
1518
+ }
1519
+ ```
1520
+
1521
+ npm installs an alias `<alias>@npm:<name>` under the alias name in `node_modules` (npm/cli `docs/lib/content/using-npm/package-spec.md`, "Aliases"; docs/exceljs-compat.md), so `import ExcelJS from 'exceljs'` and `require('exceljs')` load `@xlvane/compat`. What is supported, partly supported and not supported is listed method by method in [docs/exceljs-compat.md](https://github.com/DevXord/xlvane/blob/main/docs/exceljs-compat.md) (also in `node_modules/xlvane/docs/exceljs-compat.md` after installing xlvane).
1522
+
1523
+ This guide uses the `xlvane migrate` command of the `xlvane` package. The `xlvane` commands, the report shown below and the TypeScript examples are checked by the tests in `tests/docs/`; the npm commands were run with npm 10.9.9 on 2026-11-03.
1524
+
1525
+ ## Before you start
1526
+
1527
+ - **Node.js.** `@xlvane/compat` is an ES module and declares `engines.node` `>=22.12`. `require('exceljs')` of an ES module works without a flag from Node.js 22.12 (docs/exceljs-compat.md); `import` works too, and so do named imports such as `import { Workbook } from 'exceljs'`.
1528
+ - **Version control.** Commit `package.json` and the lockfile first, so the change can be reviewed and undone.
1529
+
1530
+ ## 1. See what would change
1531
+
1532
+ In the directory that holds the project's `package.json`:
1533
+
1534
+ ```sh
1535
+ npx xlvane migrate
1536
+ ```
1537
+
1538
+ `xlvane migrate` does the following, and nothing else:
1539
+
1540
+ 1. It reads `package.json` of the project directory (the current directory, or the directory given as the first argument). A `package.json` that is a symbolic link is refused.
1541
+ 2. It looks for an `"exceljs"` entry in `dependencies`, `devDependencies`, `optionalDependencies` and `peerDependencies`, and shows how each would change to `"npm:@xlvane/compat@^1"`. Other sections, such as `overrides`, are not changed.
1542
+ 3. It prints the Node.js requirement of `require('exceljs')` next to the project's own `engines.node`.
1543
+ 4. It scans the source files of the project (`.js`, `.mjs`, `.cjs`, `.jsx`, `.ts`, `.mts`, `.cts`, `.tsx`, up to 2 MiB each and 10 000 files) for exceljs API that `@xlvane/compat` does not support: `ModelContainer` and deep imports of `exceljs/lib/...`. Only files that contain the text `exceljs` are checked; `node_modules`, folders whose names start with a dot, and symbolic links are skipped. The matching is textual, so a finding is a place to look at, not proof.
1544
+ 5. It writes nothing unless `--force` is given.
1545
+
1546
+ For a project whose `package.json` has `"exceljs": "^4.4.0"` in `dependencies`, with a file `src/report.js` that imports `exceljs` and a file `src/legacy.cjs` that requires `exceljs/lib/xlsx/xform/base-xform`, the report is:
1547
+
1548
+ ```text
1549
+ Change in package.json:
1550
+ dependencies: - "exceljs": "^4.4.0"
1551
+ + "exceljs": "npm:@xlvane/compat@^1"
1552
+ Node.js: require('exceljs') of @xlvane/compat needs Node.js 22.12 or later (package.json engines.node: not set).
1553
+
1554
+ Scanned 2 source files for exceljs API that @xlvane/compat does not fully support:
1555
+ not supported src/legacy.cjs:1:27 deep import (exceljs/lib/...)
1556
+ Details: docs/exceljs-compat.md. Unsupported calls throw XLV_E_NOT_SUPPORTED at run time.
1557
+
1558
+ Nothing was written. Run "xlvane migrate --force" to change package.json.
1559
+ ```
1560
+
1561
+ The same report as JSON (schema in [docs/cli-json.md](https://github.com/DevXord/xlvane/blob/main/docs/cli-json.md)):
1562
+
1563
+ ```sh
1564
+ npx xlvane migrate --json
1565
+ ```
1566
+
1567
+ ## 2. Change package.json and install
1568
+
1569
+ ```sh
1570
+ npx xlvane migrate --force --dry-run # show the change without writing it
1571
+ npx xlvane migrate --force # change package.json
1572
+ npm install # install @xlvane/compat under the name exceljs
1573
+ ```
1574
+
1575
+ `--force` replaces only the value of each `"exceljs"` entry; the rest of `package.json` keeps its formatting and key order. Instead of `xlvane migrate`, the alias can also be installed directly:
1576
+
1577
+ ```sh
1578
+ npm install exceljs@npm:@xlvane/compat@^1
1579
+ ```
1580
+
1581
+ npm then saves the version it installed as the range (with npm 10.9.9 and `@xlvane/compat` 1.2.2: `"exceljs": "npm:@xlvane/compat@^1.2.2"`).
1582
+
1583
+ The code stays as it was. For example, this exceljs code runs unchanged on `@xlvane/compat`:
1584
+
1585
+ ```ts
1586
+ import ExcelJS from 'exceljs';
1587
+
1588
+ const workbook = new ExcelJS.Workbook();
1589
+ const sheet = workbook.addWorksheet('Sales');
1590
+ sheet.columns = [
1591
+ { header: 'Region', key: 'region', width: 15 },
1592
+ { header: 'Revenue', key: 'revenue', width: 12 },
1593
+ ];
1594
+ sheet.addRow({ region: 'North', revenue: 1200 });
1595
+ await workbook.xlsx.writeFile('sales.xlsx');
1596
+
1597
+ const check = new ExcelJS.Workbook();
1598
+ await check.xlsx.readFile('sales.xlsx');
1599
+ console.log(check.getWorksheet('Sales')?.getCell('B2').value); // 1200
1600
+ ```
1601
+
1602
+ In CommonJS, `const ExcelJS = require('exceljs');` gives the same object.
1603
+
1604
+ ## 3. Check the result
1605
+
1606
+ - **Run the project's tests.** Unsupported API throws an `XlvError` whose `code` is `XLV_E_NOT_SUPPORTED` and whose message names the method; nothing is dropped silently.
1607
+ - **Look at each finding of `xlvane migrate`.** Deep imports of `exceljs/lib/...` do not resolve (`exceljs/dist/es5` and the `exceljs/dist/exceljs*.js` files do); `ModelContainer` throws `XLV_E_NOT_SUPPORTED` when it is constructed.
1608
+ - **Read the differences.** [docs/exceljs-compat.md](https://github.com/DevXord/xlvane/blob/main/docs/exceljs-compat.md) lists every method with its status and the exceljs source of each behaviour, and ends with the differences found in reviews and cross-checks. The ones most likely to matter are summarised in the [@xlvane/compat README](https://github.com/DevXord/xlvane/blob/main/packages/compat/README.md#differences-from-exceljs): CSV output escapes text that starts like a formula (`formulaInjection: "off"` writes it unchanged, as exceljs does), a sheet id outside 1 to 65534 is written as the lowest unused id, only the first view of a worksheet is written, the module object is frozen, the streaming writer compresses in JavaScript, and input files are checked against ZIP and XML limits.
1609
+ - **TypeScript.** The type names of exceljs's `index.d.ts` are exported and work with `moduleResolution` `bundler` and `nodenext` and with `module: commonjs`. Under `module: node16`, TypeScript refuses `import ExcelJS = require('exceljs')` of an ES module (TS1471); use `nodenext` or `commonjs`.
1610
+ - **Browsers and Workers.** The methods that read or write files (`xlsx.readFile`, `xlsx.writeFile`, `csv.readFile`, `csv.writeFile`, the `filename` forms of the streaming classes and of `addImage`) work in Node.js, Bun and Deno; in browsers and Workers they throw `XLV_E_NOT_SUPPORTED`.
1611
+
1612
+ To handle an unsupported call in code:
1613
+
1614
+ ```ts
1615
+ import ExcelJS from 'exceljs';
1616
+
1617
+ const workbook = new ExcelJS.Workbook();
1618
+ try {
1619
+ await workbook.xlsx.readFile('report.xlsx');
1620
+ } catch (error) {
1621
+ const code = (error as { code?: unknown }).code;
1622
+ if (code === 'XLV_E_NOT_SUPPORTED') console.error('Not supported by @xlvane/compat:', (error as Error).message);
1623
+ else throw error;
1624
+ }
1625
+ ```
1626
+
1627
+ ## 4. Roll back
1628
+
1629
+ Install exceljs again under its own name, with the range the project used before (here `^4.4.0`):
1630
+
1631
+ ```sh
1632
+ npm install exceljs@^4.4.0
1633
+ ```
1634
+
1635
+ npm replaces the alias in `package.json` and `node_modules`. Restoring `package.json` and the lockfile from version control and running `npm install` has the same effect.
1636
+
1637
+ ## Reporting a problem
1638
+
1639
+ When exceljs code behaves differently on `@xlvane/compat` and the difference is not listed in docs/exceljs-compat.md, please open an issue at https://github.com/DevXord/xlvane/issues with a small script that shows it.
1640
+
1641
+ <!-- file: docs/xlsx-cves.md -->
1642
+
1643
+ # How to fix the CVEs in the `xlsx` package from npm
1644
+
1645
+ xlvane is not affiliated with SheetJS or with the ExcelJS project. Their names are used only to identify the packages this page is about; the facts below are quoted from the cited advisories and registry data, and the fixes SheetJS publishes are listed first.
1646
+
1647
+ The `xlsx` package on npm is the SheetJS Community Edition. Its latest version on npm is 0.18.5, published on 2022-03-24, and two published advisories cover that version. The fixed versions are distributed by SheetJS outside npm. This page lists the facts with their sources and two ways to deal with them: the fixed SheetJS version, or moving to xlvane. The sources were read on 2026-11-03.
1648
+
1649
+ ## The advisories
1650
+
1651
+ | CVE | GitHub advisory | What | Affected | Fixed in |
1652
+ |---|---|---|---|---|
1653
+ | CVE-2023-30533 | [GHSA-4r6h-8v6p-xvw6](https://github.com/advisories/GHSA-4r6h-8v6p-xvw6) | Prototype pollution when reading specially crafted files (CWE-1321) | SheetJS CE through 0.19.2, including every version of `xlsx` on npm | 0.19.3 |
1654
+ | CVE-2024-22363 | [GHSA-5pgg-2g8v-p4x9](https://github.com/advisories/GHSA-5pgg-2g8v-p4x9) | Regular expression denial of service, ReDoS (CWE-1333) | SheetJS CE before 0.20.2, including every version of `xlsx` on npm | 0.20.2 |
1655
+
1656
+ - GitHub rates both advisories "high" (CVSS 3.1 base scores 7.8 for CVE-2023-30533 and 7.5 for CVE-2024-22363, as listed by the npm registry's advisory data).
1657
+ - GHSA-4r6h-8v6p-xvw6 says: "Workflows that do not read arbitrary files (for example, exporting data to spreadsheet files) are unaffected."
1658
+ - Both GitHub advisories say: "A non-vulnerable version cannot be found via npm", and that the fixed version (0.19.3, and 0.20.2) "can be downloaded via https://cdn.sheetjs.com/".
1659
+ - The CVE records (published 2023-04-24 and 2024-04-05) give the same affected and fixed versions and link to SheetJS's own advisories, `https://cdn.sheetjs.com/advisories/CVE-2023-30533` and `https://cdn.sheetjs.com/advisories/CVE-2024-22363`.
1660
+
1661
+ ### Sources
1662
+
1663
+ | Source | Version | What it gives |
1664
+ |---|---|---|
1665
+ | https://registry.npmjs.org/xlsx | registry document, 2026-11-03 | `dist-tags.latest` `0.18.5`; `time["0.18.5"]` `2022-03-24T14:23:09.623Z` |
1666
+ | `POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk` with `{"xlsx":["0.18.5"]}` | answer of 2026-11-03 | both advisories for 0.18.5: GHSA-4r6h-8v6p-xvw6, `vulnerable_versions` `<0.19.3`, CVSS 7.8; GHSA-5pgg-2g8v-p4x9, `<0.20.2`, CVSS 7.5; severity "high" |
1667
+ | GitHub Advisory Database, `advisories/github-reviewed/2023/04/GHSA-4r6h-8v6p-xvw6/GHSA-4r6h-8v6p-xvw6.json` | github/advisory-database commit `887dcb68f13a7d9be8bf3d604c2e32fc3d6960e8` | alias CVE-2023-30533, details, `last_known_affected_version_range` `< 0.19.3`, CWE-1321 |
1668
+ | GitHub Advisory Database, `advisories/github-reviewed/2024/04/GHSA-5pgg-2g8v-p4x9/GHSA-5pgg-2g8v-p4x9.json` | same commit | alias CVE-2024-22363, details, `< 0.20.2`, CWE-1333 |
1669
+ | CVE records `cves/2023/30xxx/CVE-2023-30533.json` and `cves/2024/22xxx/CVE-2024-22363.json` | CVEProject/cvelistV5 commit `cc01a172ea2819376e943af6fb3d73336eacea6b` | descriptions ("SheetJS Community Edition before 0.19.3 allows Prototype Pollution via a crafted file"; for CVE-2024-22363, SheetJS Community Edition before 0.20.2 and ReDoS), publication dates, references |
1670
+ | `README.md` of github.com/SheetJS/sheetjs, branch `github` | commit `3f44ddd99c704d7ca35ea76fef7b8af2d2d435cb` | the source repository moved to https://git.sheetjs.com/sheetjs/sheetjs; "Scripts and NodeJS modules are available at https://cdn.sheetjs.com" |
1671
+
1672
+ The advisory files and CVE records were read through raw.githubusercontent.com at the commits given. The GitHub advisory web pages, the SheetJS sites (sheetjs.com, docs.sheetjs.com, cdn.sheetjs.com, git.sheetjs.com) and other advisory sites could not be reached from the environment where this page was written, so they are linked but not quoted.
1673
+
1674
+ ## Route 1: the fixed SheetJS Community Edition
1675
+
1676
+ The fixed versions of the Community Edition (0.19.3 and later) are distributed by SheetJS from its own site, not from npm: the advisories name https://cdn.sheetjs.com/ for both fixes. The SheetJS README points to its documentation at https://docs.sheetjs.com and says "Scripts and NodeJS modules are available at https://cdn.sheetjs.com". Install the fixed version as the SheetJS documentation describes; this page does not copy an install command, because that documentation could not be read when the page was written. This route keeps the SheetJS API; check the SheetJS changelog (https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/CHANGELOG.md, linked from the CVE-2023-30533 record) for changes between 0.18.5 and the version you install.
1677
+
1678
+ ## Route 2: move to xlvane
1679
+
1680
+ xlvane is a different library with a different API; it is not a drop-in replacement for `xlsx`. A package with a SheetJS-compatible API, `@xlvane/xlsx-compat`, is planned as a later phase ([ROADMAP.md](https://github.com/DevXord/xlvane/blob/main/ROADMAP.md)); it does not exist yet.
1681
+
1682
+ Facts about the package (npm registry, 2026-11-03; [docs/facts/comparison.md](https://github.com/DevXord/xlvane/blob/main/docs/facts/comparison.md)):
1683
+
1684
+ - `xlvane` is installed from npm (`npm install xlvane`), under the MIT license, with one runtime dependency, `fflate`.
1685
+ - Versions from 1.0.1 are published from GitHub Actions with npm provenance ([SECURITY.md](https://github.com/DevXord/xlvane/blob/main/SECURITY.md#supported-versions)).
1686
+ - It reads XLSX, XLSM, XLTX and XLTM, writes XLSX, and converts to and from CSV and JSON. It does not read legacy `.xls`, XLSB, ODS, Numbers or the other formats SheetJS supports, does not open encrypted workbooks, and does not calculate formulas.
1687
+ - How it treats input files from untrusted sources (ZIP and XML limits, prototype pollution, amplification by small files, CSV formula injection) is described in [SECURITY.md](https://github.com/DevXord/xlvane/blob/main/SECURITY.md#what-the-library-defends-against).
1688
+
1689
+ ### Equivalent calls
1690
+
1691
+ The SheetJS names are those of the `xlsx` 0.18.5 README ("Interface"). xlvane's functions take the file's bytes (`Uint8Array` or `ArrayBuffer`) or a workbook already read; `xlvane/node` adds file helpers for Node.js.
1692
+
1693
+ | SheetJS (`xlsx` 0.18.5) | xlvane |
1694
+ |---|---|
1695
+ | `XLSX.read(data)` | `readWorkbook(bytes)` |
1696
+ | `XLSX.readFile(path)` | `await readWorkbookFile(path)` from `xlvane/node` |
1697
+ | `workbook.SheetNames` | `workbook.sheets.map((sheet) => sheet.name)` |
1698
+ | `XLSX.utils.sheet_to_json(worksheet)` | `readTable(bytes, { sheet }).rows`, or `toJSON(bytes, { sheet }).rows` for JSON-ready values |
1699
+ | `XLSX.utils.sheet_to_csv(worksheet)` | `toCSV(bytes, { sheet })` |
1700
+ | `XLSX.utils.json_to_sheet(rows)`, `book_new`, `book_append_sheet`, `XLSX.write(...)` | `writeTable(rows, { sheet })` or `fromJSON(rows)`, which return the `.xlsx` file as a `Uint8Array` |
1701
+ | `XLSX.writeFile(workbook, path)` | `writeFile(path, writeTable(rows))` with `node:fs/promises`, or `writeWorkbookFile(workbook, path)` from `xlvane/node` for a workbook built with `createWorkbook` |
1702
+
1703
+ ```ts
1704
+ import { readTable, toCSV, toJSON, writeTable } from 'xlvane';
1705
+ import { readWorkbookFile } from 'xlvane/node';
1706
+ import { writeFile } from 'node:fs/promises';
1707
+
1708
+ const workbook = await readWorkbookFile('orders.xlsx');
1709
+ console.log(workbook.sheets.map((sheet) => sheet.name)); // like workbook.SheetNames
1710
+
1711
+ // Rows as objects keyed by the header row (detected like inspect(); `header: 1` takes row 1).
1712
+ const { rows } = readTable(workbook, { sheet: 'Orders' });
1713
+ // The same rows with dates as ISO text, ready for JSON.stringify.
1714
+ const json = toJSON(workbook, { sheet: 'Orders' }).rows;
1715
+ // CSV; text that starts like a formula gets a leading ' (formulaInjection: 'off' turns this off).
1716
+ const csv = toCSV(workbook, { sheet: 'Orders' });
1717
+
1718
+ // A new .xlsx file from objects: bold header, frozen first row, filter, fitted widths.
1719
+ const bytes = writeTable(rows, { sheet: 'Orders' });
1720
+ await writeFile('orders-copy.xlsx', bytes);
1721
+ console.log(json.length, csv.length);
1722
+ ```
1723
+
1724
+ Differences to expect:
1725
+
1726
+ - `readTable` finds the header row the way `inspect()` does (`header: 'auto'`), or takes `header: 3` (a 1-based row) or `header: false` (keys are the column letters). It returns `Date` objects for date cells; `toJSON` and `toCSV` give ISO 8601 text.
1727
+ - Unknown sheet names and damaged files throw an `XlvError` with a stable `XLV_E_*` code, a location and a hint ([docs/errors.md](https://github.com/DevXord/xlvane/blob/main/docs/errors.md)).
1728
+ - Read limits (cells, decompressed bytes, XML size) apply by default and can be raised per call: `readWorkbook(bytes, { limits: { maxCells: 50_000_000 } })`. The defaults are in the [package README](https://github.com/DevXord/xlvane/blob/main/packages/xlvane/README.md#untrusted-input-and-limits).
1729
+
1730
+ The full API is described in the [package README](https://github.com/DevXord/xlvane/blob/main/packages/xlvane/README.md) (also in `node_modules/xlvane/README.md` after installing).
1731
+
1433
1732
  <!-- file: docs/cli-json/common.schema.json -->
1434
1733
 
1435
1734
  {
@@ -2500,6 +2799,80 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
2500
2799
  "additionalProperties": false
2501
2800
  }
2502
2801
  },
2802
+ "chartCount": {
2803
+ "type": "integer",
2804
+ "minimum": 0
2805
+ },
2806
+ "charts": {
2807
+ "type": "array",
2808
+ "items": {
2809
+ "type": "object",
2810
+ "properties": {
2811
+ "cell": {
2812
+ "anyOf": [
2813
+ {
2814
+ "type": "null"
2815
+ },
2816
+ {
2817
+ "type": "string"
2818
+ }
2819
+ ]
2820
+ },
2821
+ "types": {
2822
+ "type": "array",
2823
+ "items": {
2824
+ "type": "string"
2825
+ }
2826
+ },
2827
+ "typesTruncated": {
2828
+ "type": "boolean"
2829
+ },
2830
+ "title": {
2831
+ "anyOf": [
2832
+ {
2833
+ "type": "null"
2834
+ },
2835
+ {
2836
+ "type": "object",
2837
+ "properties": {
2838
+ "text": {
2839
+ "type": "string"
2840
+ }
2841
+ },
2842
+ "required": [
2843
+ "text"
2844
+ ],
2845
+ "additionalProperties": false
2846
+ },
2847
+ {
2848
+ "type": "object",
2849
+ "properties": {
2850
+ "ref": {
2851
+ "type": "string"
2852
+ }
2853
+ },
2854
+ "required": [
2855
+ "ref"
2856
+ ],
2857
+ "additionalProperties": false
2858
+ }
2859
+ ]
2860
+ },
2861
+ "seriesCount": {
2862
+ "type": "integer",
2863
+ "minimum": 0
2864
+ }
2865
+ },
2866
+ "required": [
2867
+ "cell",
2868
+ "types",
2869
+ "typesTruncated",
2870
+ "title",
2871
+ "seriesCount"
2872
+ ],
2873
+ "additionalProperties": false
2874
+ }
2875
+ },
2503
2876
  "protected": {
2504
2877
  "type": "boolean"
2505
2878
  },
@@ -2626,6 +2999,8 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
2626
2999
  "noteCount",
2627
3000
  "imageCount",
2628
3001
  "images",
3002
+ "chartCount",
3003
+ "charts",
2629
3004
  "notes",
2630
3005
  "protected",
2631
3006
  "printArea",