xlvane 1.0.1 → 1.1.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.0.1: documentation in one file
1
+ # xlvane 1.1.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
 
@@ -111,7 +111,7 @@ In Node.js, `writeWorkbookFile(workbook, 'sales.xlsx')` from `xlvane/node` write
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
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.
114
- - Workbook: `workbook.views([{ x, y, width, height, firstSheet, activeTab, visibility }])` sets the workbook windows and `workbook.calculation({ fullCalcOnLoad: true })` asks applications to recalculate when the file is opened.
114
+ - 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
115
  - 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
116
  - 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.
117
117
  - The output is deterministic: the same workbook always gives the same bytes. The exception is a protection password (`protect({ password })`), whose verifier gets a random salt; pass a fixed salt through `hashPassword` for identical bytes. The test suite opens written files that use all of these features in LibreOffice and checks the values, styles and layout it sees.
@@ -157,7 +157,7 @@ console.log(read.hyperlinks, read.notes, read.cell('B2')?.note?.text, read.tabCo
157
157
 
158
158
  - **Hyperlinks:** `link(range, { url | location, tooltip?, display? })`. A `url` (web page, `mailto:`, file) is stored as an external relationship of the sheet; a `location` (a place in the workbook) is stored without one. The cell text is set with `set`. Reading gives `sheet.hyperlinks` and `cell.hyperlink`; xlvane never opens a target, and `XLV_W_UNUSUAL_HYPERLINK` reports targets other than http(s) or mailto.
159
159
  - **Notes (comments):** `note(cell, text | { text | runs, author?, box? })`; `box` sets the note box: `margins` in millimetres, `autoMargins`, `locked`, `lockText`, `anchor` (`'absolute'`, `'oneCell'`, `'twoCell'`); reading gives the same as `note.box`. The cell needs no value. Notes are written hidden, as spreadsheet applications show them when the pointer rests on the cell. Reading gives `sheet.notes` and `cell.note` (`text`, `author`, and `runs` for rich text). Threaded comments of current Excel versions are not read; their placeholder notes are, with `XLV_W_THREADED_COMMENTS`.
160
- - **Tab colour and view:** `tabColor(color)`, and `view({ zoom, zoomScaleNormal, showGridLines, showRowColHeaders, showRuler, rightToLeft, layout, split, topLeftCell, activeCell })`. Reading gives `sheet.tabColor`, `sheet.view`, `sheet.properties`, `workbook.views` and `workbook.calculation`.
160
+ - **Tab colour and view:** `tabColor(color)`, and `view({ zoom, zoomScaleNormal, showGridLines, showRowColHeaders, showRuler, rightToLeft, layout, split, topLeftCell, activeCell })`; a split window takes `split: { x, y, topLeftCell }` in points, where `topLeftCell` is the first cell shown in the bottom-right pane. Reading gives `sheet.tabColor`, `sheet.view`, `sheet.properties`, `workbook.views` and `workbook.calculation`.
161
161
  - **Streaming:** `writeWorkbookStream` takes `hyperlinks` and `notes` per row (`{ values, hyperlinks: [...], notes: [...] }`, by column position) and `tabColor`/`view` per sheet. `openWorkbookStream` sets `cell.note` on the rows it returns and lists a sheet's notes with `notes(sheet)`; it does not set `cell.hyperlink`.
162
162
  - `editCells` and `appendRows` keep existing links and notes but cannot add new ones yet.
163
163
 
@@ -299,10 +299,10 @@ const { bytes: shorter } = deleteRows(wider, { sheet: 'Orders', at: 10, count: 3
299
299
  ```
300
300
 
301
301
  - Like `editCells`, they change an existing file in place and keep every other part as it was.
302
- - References follow the rows as in a spreadsheet application: formulas on every sheet, merged cells, hyperlinks, notes, the autofilter, conditional formats, data validation, page breaks and defined names. A range grows when rows are inserted inside it and shrinks when some of its rows are deleted; a reference whose cells are all deleted becomes `#REF!`. The rules are in docs/facts/ref-adjust.md (observed in LibreOffice; not yet checked against Excel).
302
+ - References follow the rows as in a spreadsheet application: formulas on every sheet, merged cells, hyperlinks, notes, the autofilter, conditional formats (with their thresholds), data validation, page breaks and defined names. A range grows when rows are inserted inside it and shrinks when some of its rows are deleted; a reference whose cells are all deleted becomes `#REF!`. The rules are in docs/facts/ref-adjust.md (observed in LibreOffice; not yet checked against Excel).
303
303
  - Tables move, grow and shrink with their rows; deleting a table's header row, totals row or all of its data rows is refused.
304
304
  - Pictures move with their rows. Drawings with charts, shapes or form controls, a deletion that would cut a picture, parts xlvane cannot rewrite (charts or pivot tables that may refer to the sheet), and formulas xlvane cannot rewrite safely (for example 3-D references such as `Jan:Mar!A1` that include the sheet) are refused with `XLV_E_NOT_SUPPORTED`. Data that would be pushed past row 1048576 gives `XLV_E_LIMIT_EXCEEDED`.
305
- - They also refuse sheets whose conditional formats use the [MS-XLSX] extension, which xlvane writes for data bars with an extension setting (`gradient`, `borderColor`, `negativeFillColor`, `negativeBorderColor`, `axisPosition`, `axisColor`, `direction`, or an `autoMin`/`autoMax` threshold) and for the icon sets `3Stars`, `3Triangles`, `5Boxes` and `NoIcons`; insert the rows before adding such rules, or add the rules again after the change.
305
+ - Conditional formats and data validations of the [MS-XLSX] extension (extended data bars, the icon sets `3Stars`, `3Triangles`, `5Boxes` and `NoIcons`, and rules written by Excel) move with the rows too, and so do thresholds that refer to cells (`{ type: 'formula', value: '$C$12' }`). Other extended content that refers to cells, such as sparklines or slicers, is refused.
306
306
  - Sheet views (selection, frozen panes) are not changed.
307
307
  - `adjustFormula` and `adjustRange` apply the same rules to formula text you hold yourself.
308
308
 
@@ -391,10 +391,10 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
391
391
  | `maxXmlDepth` | 128 | deeply nested XML |
392
392
  | `maxXmlAttributes` | 256 | elements with huge numbers of attributes |
393
393
  | `maxXmlNameLength` | 1 024 | huge element or attribute names |
394
- | `maxXmlTextLength` | 8 Mi characters | huge text nodes or attribute values |
394
+ | `maxXmlTextLength` | 8 Mi characters | huge text nodes, attribute values, comments or processing instructions |
395
395
  | `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) |
397
- | `maxSharedStringChars` | 512 Mi characters | many cells pointing at one huge shared string (a few MB that become many GB when values are copied) |
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 |
397
+ | `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 |
398
398
 
399
399
  ## Package size
400
400
 
@@ -409,6 +409,7 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
409
409
 
410
410
  ## Links
411
411
 
412
+ - Website with the guide and an in-browser demo: [xlvane.github.io](https://xlvane.github.io)
412
413
  - Changes in each version: [CHANGELOG.md](https://github.com/DevXord/xlvane/blob/main/CHANGELOG.md)
413
414
  - Bugs and questions: [GitHub issues](https://github.com/DevXord/xlvane/issues)
414
415
  - Reporting a vulnerability: [SECURITY.md](https://github.com/DevXord/xlvane/blob/main/SECURITY.md)
@@ -586,7 +587,7 @@ const { bytes: wider } = insertRows(bytes, { sheet: 'Sales', at: 5, count: 2, st
586
587
  const { bytes: shorter } = deleteRows(wider, { sheet: 'Sales', at: 5, count: 2 });
587
588
  ```
588
589
 
589
- Formulas on every sheet, merged cells, links, notes, conditional formats, data validation and defined names follow the rows; references to deleted cells become `#REF!`. Tables and pictures move with their rows. Drawings with charts or shapes, and a deletion that would cut a picture or remove a table's header row, totals row or all of its data rows, are refused with `XLV_E_NOT_SUPPORTED`.
590
+ Formulas on every sheet, merged cells, links, notes, conditional formats (also extended data bars and icon sets, and thresholds that refer to cells), data validation and defined names follow the rows; references to deleted cells become `#REF!`. Tables and pictures move with their rows. Drawings with charts or shapes, sparklines and slicers, and a deletion that would cut a picture or remove a table's header row, totals row or all of its data rows, are refused with `XLV_E_NOT_SUPPORTED`.
590
591
 
591
592
  `inspect()` reports the same items (`properties`, `definedNames`, and per sheet `richTextCellCount`, `styledColumns`, `hyperlinkCount` and `noteCount`). A column style is a default for cells added later; xlvane does not apply it to cells you set.
592
593
 
@@ -693,11 +694,13 @@ Each code links to its section, which says what to do. The CLI prints the code a
693
694
  | [`XLV_W_MACROS_PRESENT`](#xlv_w_macros_present) | The workbook contains a VBA project or Excel 4.0 macro sheets. |
694
695
  | [`XLV_W_EXTERNAL_LINKS`](#xlv_w_external_links) | The workbook links to other workbooks. |
695
696
  | [`XLV_W_DUPLICATE_CELL`](#xlv_w_duplicate_cell) | A sheet defines the same cell more than once. |
697
+ | [`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. |
696
698
  | [`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 … |
697
699
  | [`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 … |
698
700
  | [`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. |
699
701
  | [`XLV_W_THREADED_COMMENTS`](#xlv_w_threaded_comments) | A sheet has threaded comments, the conversations of current Excel versions ([MS-XLSX] §2.3.7). |
700
702
  | [`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). |
703
+ | [`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. |
701
704
  | [`XLV_W_PART_MISSING`](#xlv_w_part_missing) | The workbook points to a styles or shared strings part that does not exist. |
702
705
  | [`XLV_W_FORMULAS_NOT_RECALCULATED`](#xlv_w_formulas_not_recalculated) | Reported by `editCells` and `appendRows` when the workbook contains formulas. |
703
706
  | [`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 … |
@@ -721,6 +724,12 @@ An argument of a method of `@xlvane/compat` is not valid, in a case where excelj
721
724
  ### XLV_E_LIMIT_EXCEEDED
722
725
  The file exceeds one of the resource limits (`ReadLimits`): input size, number of ZIP entries, decompressed size of one entry or of all entries, compression ratio, XML nesting depth, attribute count, name or text length, number of elements in a tree-parsed part, or number of cells. `details.limit` names the limit and `details.value` its current value. Sizes are measured on the bytes actually produced by decompression, so a file cannot avoid a limit by declaring false sizes.
723
726
 
727
+ Some limits also bound work derived from a small file (1.1.1, D-H01):
728
+
729
+ - `maxCells` (`details.limit` `maxCells`, `details.value` the budget): `toCSV`, `readTable`, `toJSON`, `readTableStream`, `xlvane read` and `xlvane convert` write at most `max(maxCells, 16 × cells read)` positions (rows × columns of the output), so a few cells far apart cannot make them produce a value for every position between; `@xlvane/compat`'s `xlsx.load` creates at most as many cells for merged ranges, tables and column definitions, and looks up at most 64 Mi rows (or 16 per cell read) for row settings up to the sheets' dimensions. Pass a smaller `range`, or raise `maxCells` for a trusted file.
730
+ - `maxXmlTextLength`: also the length of an XML comment or processing instruction.
731
+ - `maxSharedStringChars`: also the text of shared formulas, counted apart from shared strings: the length of each sharing cell's formula, as moved to that cell.
732
+
724
733
  `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.
725
734
 
726
735
  `NewSheet.validate` throws it when a sheet would get more than 65534 data validations ([MS-OI29500] §2.1.614 g; `details.limit` `dataValidations`). When reading, the conditional formatting and data validations of one sheet count against `maxXmlTreeElements`.
@@ -794,6 +803,7 @@ A part exists but has the wrong structure: for example the workbook part's root
794
803
  | `target-mode` | `TargetMode` is neither `Internal` nor `External` |
795
804
  | `internal-target` | an internal relationship target cannot be resolved to a part name |
796
805
  | `equivalent-part-names` | two ZIP entries map to equivalent part names (e.g. a raw UTF-8 name and its percent-encoded form) |
806
+
797
807
  **Fix:** check that the file is an Excel workbook (not a Word or PowerPoint document) and re-save it in a spreadsheet application.
798
808
 
799
809
  ### XLV_E_INVALID_CELL_REF
@@ -887,7 +897,7 @@ The CSV text given to `fromCSV` has a quoted field that is never closed, text ri
887
897
  `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).
888
898
  `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.
889
899
  `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).
890
- `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 data bars or sparklines, 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.
900
+ `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.
891
901
  **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.
892
902
  For `insertRows`/`deleteRows`, insert or delete the rows in a spreadsheet application, or remove the named content first.
893
903
 
@@ -921,6 +931,10 @@ Some merged ranges have values in cells other than the top-left one. Spreadsheet
921
931
  `editCells` or `appendRows` wrote a value into a cell of a merged range other than its top-left cell. The value is stored, but spreadsheet applications show only the top-left cell of a merged range, so the value is not visible. `cells` lists the cells written (up to ten); the message names the first one and its merged range.
922
932
  **Fix:** write the value into the top-left cell of the range (named in the message), or unmerge the range first.
923
933
 
934
+ ### XLV_W_LIMIT_REACHED
935
+ 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).
936
+ **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.
937
+
924
938
  ### XLV_W_VERY_HIDDEN_SHEET
925
939
  A sheet has the state `veryHidden`: spreadsheet applications offer no menu command to unhide it, so users usually do not know it exists.
926
940
  **Fix:** nothing, if the sheet is expected; `inspect` lists it with its state, and `workbook.sheets` includes it.
@@ -957,6 +971,10 @@ A sheet has threaded comments, the conversations of current Excel versions ([MS-
957
971
  A sheet listed in the workbook has no part (the relationship or the part itself is missing). The sheet is returned with `part: null` and no cells.
958
972
  **Fix:** re-save the file in the application that made it; the sheet's cells are not in the file.
959
973
 
974
+ ### XLV_W_SHEET_PART_SHARED
975
+ 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.
976
+ **Fix:** re-save the file in the application that made it, and check whether the sheets returned without cells should hold data.
977
+
960
978
  ### XLV_W_PART_MISSING
961
979
  The workbook points to a styles or shared strings part that does not exist. Reading continues without it: without styles, date-formatted numbers are returned as numbers; without shared strings, shared-string cells are treated as empty.
962
980
  **Fix:** re-save the file in the application that made it; check number formats and text cells of the result.
@@ -1007,6 +1025,7 @@ A worksheet contains a row whose number is not a whole number from 1 to 1048576,
1007
1025
  |---|---|---|
1008
1026
  | `name` | string | sheet name |
1009
1027
  | `index` | number | 0-based position |
1028
+ | `sheetId` | number or `null` | the sheet's id in the file (`sheet@sheetId`), independent of its position; `null` when the file has none, an invalid one or a repeated one |
1010
1029
  | `kind` | `"worksheet" \| "chartsheet" \| "dialogsheet" \| "macrosheet"` | only worksheets have cells |
1011
1030
  | `state` | `"visible" \| "hidden" \| "veryHidden"` | visibility |
1012
1031
  | `dimension` | string or `null` | range declared in the file, e.g. `"A1:D20"` |
@@ -1085,6 +1104,7 @@ Values in `samples` are JSON-safe: numbers, booleans, strings (cut to 100 charac
1085
1104
  {
1086
1105
  "name": "Sales",
1087
1106
  "index": 0,
1107
+ "sheetId": 1,
1088
1108
  "kind": "worksheet",
1089
1109
  "state": "visible",
1090
1110
  "dimension": "A1:C3",
@@ -1241,7 +1261,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1241
1261
  | `xlsx.write(stream)` | supported | | `lib/xlsx/xlsx.js` L643 |
1242
1262
  | `xlsx.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/xlsx/xlsx.js` L238 |
1243
1263
  | `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 |
1244
- | `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 or LF is written with a leading `'` (CSV injection, docs/facts/csv.md CSV-10; `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`. | `lib/csv/csv.js` L109–186 |
1264
+ | `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`. | `lib/csv/csv.js` L109–186 |
1245
1265
  | `csv.createInputStream()` | supported | Throws the deprecation error, as in exceljs 4. | `lib/csv/csv.js` L101 |
1246
1266
  | `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 |
1247
1267
  | `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) |
@@ -1250,7 +1270,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1250
1270
 
1251
1271
  | API | Status | Notes | Source in exceljs |
1252
1272
  |---|---|---|---|
1253
- | `name`, `id`, `state` | partly | Ids are given as in exceljs (the first free id), but the file gets `sheetId` 1, 2, 3… in sheet order and a loaded workbook numbers its sheets 1, 2, 3… in file order, because xlvane's core neither writes given sheet ids nor reports them on reading. exceljs writes `id` as `sheetId` and keeps it on loading, so after removing a sheet, `getWorksheet(id)` can give another sheet than with exceljs; look sheets up by name to be safe. | `lib/doc/worksheet.js` L33; `lib/doc/workbook.js` L43–51; `lib/xlsx/xform/book/sheet-xform.js` L7, L18 |
1273
+ | `name`, `id`, `state` | partly | Ids are given as in exceljs (the first free id), written as `sheetId` and kept on loading and by the `model` setter, as exceljs does, so `getWorksheet(id)` finds the same sheet after removing a sheet, writing and loading (D-F03). Differences: an id outside 1 to 65534, the range Office uses (W2-20), is written as the lowest unused `sheetId`; a file's missing, repeated or larger than 65534 `sheetId` gives the worksheet the lowest free id (exceljs reads `NaN`, lets the later sheet replace the earlier one, or uses the large number as an array index); the streaming `WorkbookReader` numbers worksheets 1, 2, 3… in file order, where exceljs uses the number N of the part `xl/worksheets/sheetN.xml` (`lib/stream/xlsx/workbook-reader.js` L107-109, L293-297). | `lib/doc/worksheet.js` L33; `lib/doc/workbook.js` L43–51; `lib/xlsx/xform/book/sheet-xform.js` L7, L18 |
1254
1274
  | `columns` (get / set `{ header, key, width, style }`) | supported | As in exceljs, a column exists for every column a cell was created in, also after `addRow` and on a loaded file without `<cols>`, so `columns.forEach(c => { c.eachCell(…); c.width = … })` works; columns without width, style, hidden flag or outline level are not written (D-R09). An empty sheet gives `[]` where exceljs gives `null`. | `lib/doc/worksheet.js` L203, L209, L243–261; `lib/doc/row.js` L48 |
1255
1275
  | `getColumn(key \| letter \| number)` | supported | | `lib/doc/worksheet.js` L243 |
1256
1276
  | `addRow(values \| object, style?)`, `addRows` | supported | Style `'i'` / `'i+'` copies the style of the row above, as in exceljs. | `lib/doc/worksheet.js` L406, L414 |
@@ -1261,7 +1281,7 @@ Status: **supported** means the same behaviour as exceljs for the cases covered
1261
1281
  | `rowCount`, `actualRowCount`, `columnCount` | supported | | `lib/doc/worksheet.js` L374, L378, L313 |
1262
1282
  | `mergeCells(range \| t, l, b, r)`, `unMergeCells` | supported | | `lib/doc/worksheet.js` L602, L656 |
1263
1283
  | `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 |
1264
- | `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 get the cell right of and below the panes as `topLeftCell`, a split view none (exceljs's split `topLeftCell` is the top-left cell of the bottom-right pane, `pane@topLeftCell`, which xlvane's core does not write), but 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 |
1284
+ | `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 |
1265
1285
  | `autoFilter` (range string) | supported | | `lib/doc/worksheet.js` L116 |
1266
1286
  | `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 |
1267
1287
  | `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 |
@@ -2205,6 +2225,13 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
2205
2225
  "type": "integer",
2206
2226
  "minimum": 0
2207
2227
  },
2228
+ "sheetId": {
2229
+ "type": [
2230
+ "integer",
2231
+ "null"
2232
+ ],
2233
+ "minimum": 0
2234
+ },
2208
2235
  "kind": {
2209
2236
  "enum": [
2210
2237
  "worksheet",
@@ -2555,6 +2582,7 @@ A workbook loaded with `xlsx.load()` is written again by `xlsx.writeBuffer()` (`
2555
2582
  "required": [
2556
2583
  "name",
2557
2584
  "index",
2585
+ "sheetId",
2558
2586
  "kind",
2559
2587
  "state",
2560
2588
  "dimension",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlvane",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Read, inspect and write XLSX files; input is treated as untrusted. ESM only, one runtime dependency (fflate).",
5
5
  "keywords": [
6
6
  "xlsx",
@@ -167,7 +167,7 @@ const { bytes: wider } = insertRows(bytes, { sheet: 'Sales', at: 5, count: 2, st
167
167
  const { bytes: shorter } = deleteRows(wider, { sheet: 'Sales', at: 5, count: 2 });
168
168
  ```
169
169
 
170
- Formulas on every sheet, merged cells, links, notes, conditional formats, data validation and defined names follow the rows; references to deleted cells become `#REF!`. Tables and pictures move with their rows. Drawings with charts or shapes, and a deletion that would cut a picture or remove a table's header row, totals row or all of its data rows, are refused with `XLV_E_NOT_SUPPORTED`.
170
+ Formulas on every sheet, merged cells, links, notes, conditional formats (also extended data bars and icon sets, and thresholds that refer to cells), data validation and defined names follow the rows; references to deleted cells become `#REF!`. Tables and pictures move with their rows. Drawings with charts or shapes, sparklines and slicers, and a deletion that would cut a picture or remove a table's header row, totals row or all of its data rows, are refused with `XLV_E_NOT_SUPPORTED`.
171
171
 
172
172
  `inspect()` reports the same items (`properties`, `definedNames`, and per sheet `richTextCellCount`, `styledColumns`, `hyperlinkCount` and `noteCount`). A column style is a default for cells added later; xlvane does not apply it to cells you set.
173
173