xlvane 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ From a shell: `npx xlvane inspect orders.xlsx`, then `npx xlvane read orders.xls
26
26
  ## Contents
27
27
 
28
28
  - [Command line in 5 minutes](#command-line-in-5-minutes)
29
- - [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))
29
+ - [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))
30
30
  - [Tasks in one call](#tasks-in-one-call): tables of rows, CSV, JSON, editing files, diff · [inserting and deleting rows](#inserting-and-deleting-rows)
31
31
  - [Large files: streaming](#large-files-streaming)
32
32
  - [Inspecting a workbook](#inspecting-a-workbook) · [Errors and warnings](#errors-and-warnings) · [Untrusted input and limits](#untrusted-input-and-limits)
@@ -172,10 +172,41 @@ for (const picture of read.getSheet('Report').images) console.log(picture.anchor
172
172
  ```
173
173
 
174
174
  - **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.
175
- - **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.
175
+ - **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.
176
176
  - **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`.
177
177
  - **Streaming:** `writeWorkbookStream({ images: [png], sheets: [{ images: [{ image: 0, anchor: 'B2:D6' }], background: 0, rows }] })`.
178
178
 
179
+ ### Charts
180
+
181
+ ```ts
182
+ import { createWorkbook, readWorkbook, writeWorkbook } from 'xlvane';
183
+
184
+ const workbook = createWorkbook();
185
+ const sales = workbook.addSheet('Sales').set('A1', 'Month').set('B1', 'North').set('C1', 'South');
186
+ ['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));
187
+ sales.addChart({
188
+ type: 'column',
189
+ anchor: 'E2:L18',
190
+ title: 'Sales by month',
191
+ series: [
192
+ { name: 'Sales!B1', categories: 'Sales!A2:A4', values: 'Sales!B2:B4' },
193
+ { name: 'Sales!C1', categories: 'Sales!A2:A4', values: 'Sales!C2:C4', color: 'C00000' },
194
+ ],
195
+ legend: 'bottom',
196
+ axes: { y: { title: 'Units', min: 0 } },
197
+ });
198
+
199
+ const chart = readWorkbook(writeWorkbook(workbook)).getSheet('Sales').charts[0];
200
+ 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']
201
+ ```
202
+
203
+ - **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`.
204
+ - **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.
205
+ - **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.
206
+ - **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.
207
+ - **Rows:** `insertRows` and `deleteRows` refuse sheets with charts, and workbooks whose charts may refer to the sheet, with `XLV_E_NOT_SUPPORTED`.
208
+ - **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.
209
+
179
210
  ### Sheet rules and printing
180
211
 
181
212
  ```ts
@@ -330,7 +361,7 @@ const stream = writeWorkbookStream({ sheets: [{ name: 'Data', rows: rows() }] })
330
361
  - Memory stays constant: the test suite reads and writes 50 000 and 400 000 rows and checks that the retained heap does not grow. It measured under 1 MB in both cases.
331
362
  - Streamed text is stored in the cells (inline strings) by default. Pass `sharedStrings: true` for a shared string table, which is smaller when values repeat but kept in memory until the end.
332
363
  - Rows come in file order. A shared formula whose master cell comes later in the file cannot be translated while streaming.
333
- - `maxCells` does not apply when streaming; single parts may decompress to 4 GiB (`STREAM_LIMIT_DEFAULTS`). All other limits apply.
364
+ - `maxCells` does not apply when streaming, except that one `<row>` element may hold at most 16 384 cells; single parts may decompress to 4 GiB (`STREAM_LIMIT_DEFAULTS`). All other limits apply.
334
365
  - `readTableStream` differs from `readTable` in what it can see in advance: it detects the header row among the first rows it reads and fixes the columns there (the columns of the header row, or of the first rows without one), so a column that first appears further down is not part of the objects. `readTable` takes the columns of the whole sheet. For files that fit in memory, `readTable` gives the complete table.
335
366
 
336
367
  ## Benchmark
@@ -387,8 +418,8 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
387
418
  | `maxXmlNameLength` | 1 024 | huge element or attribute names |
388
419
  | `maxXmlTextLength` | 8 Mi characters | huge text nodes, attribute values, comments or processing instructions |
389
420
  | `maxXmlTreeElements` | 1 000 000 | huge workbook, styles or relationship parts (elements plus attributes) |
390
- | `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 |
391
- | `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 |
421
+ | `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 |
422
+ | `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` |
392
423
 
393
424
  ## Package size
394
425
 
@@ -398,7 +429,7 @@ Limits can be changed per call, for example `readWorkbook(bytes, { limits: { max
398
429
 
399
430
  - 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.
400
431
  - 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.
401
- - 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.
432
+ - 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.
402
433
  - The command line reads files, not standard input.
403
434
 
404
435
  ## Links
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { On as XlvError, Vt as suggest, _ as isoDate, b as readWorkbook, dn as parseRange, g as inspect } from "./options-Ba1iWD-Z.js";
3
- import { a as appendRows, c as fromCSV, d as fromJSON, f as pickSheet, h as toJSON, l as inferValue, n as insertRows, o as editCells, p as readTable, s as diff, t as deleteRows, u as toCSV } from "./rows-IWKIxHt9.js";
4
- import { i as readInput } from "./node-files-CDb6TyZb.js";
2
+ import { Vt as suggest, _ as inspect, v as isoDate, x as readWorkbook, yn as parseRange, zn as XlvError } from "./options-Cxt-LJE4.js";
3
+ import { a as appendRows, c as fromCSV, d as fromJSON, f as pickSheet, h as toJSON, l as inferValue, n as insertRows, o as editCells, p as readTable, s as diff, t as deleteRows, u as toCSV } from "./rows-DltZ-hiW.js";
4
+ import { i as readInput } from "./node-files-SVg17pky.js";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
- import { chmod, lstat, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
6
+ import { lstat, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
7
7
  import process from "node:process";
8
8
  import { createRequire } from "node:module";
9
9
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
@@ -523,14 +523,15 @@ function oneLine(text) {
523
523
  }
524
524
  /**
525
525
  * Control characters that a terminal would act on (C0 except tab and line feed, DEL, and C1,
526
- * which includes the 8-bit CSI U+009B) written as `\x1b`, and bidirectional formatting characters
527
- * written as `\u202e`, so text from a file, such as a cell
526
+ * which includes the 8-bit CSI U+009B) written as `\x1b`, bidirectional formatting characters
527
+ * written as `\u202e`, and line feeds written as `⏎` (so a name from a file cannot add lines of its
528
+ * own to the output, D-K04), so text from a file, such as a cell
528
529
  * holding `_x001B_]0;title_x0007_`, cannot move the cursor, change the window title or hide
529
530
  * output (docs/decisions.md D-R02). Used for all human-readable output; `--json` writes the same
530
531
  * characters as `\uXXXX` escapes instead (D-H01).
531
532
  */
532
533
  function escapeControls(text) {
533
- return text.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`).replace(BIDI, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
534
+ return text.replace(/\n/g, "⏎").replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`).replace(BIDI, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
534
535
  }
535
536
  /**
536
537
  * The characters with the Unicode property Bidi_Control (checked against `/\p{Bidi_Control}/u` in
@@ -649,6 +650,18 @@ function quoteArg(arg) {
649
650
  if (/^[\w@%+=:,./-]+$/.test(arg)) return arg;
650
651
  return `'${arg.replace(/'/g, `'\\''`)}'`;
651
652
  }
653
+ /**
654
+ * `args` with `options` added before the first "--", after which every argument is positional
655
+ * (or at the end when there is none).
656
+ */
657
+ function withOptions(args, ...options) {
658
+ const at = args.indexOf("--");
659
+ return at === -1 ? [...args, ...options] : [
660
+ ...args.slice(0, at),
661
+ ...options,
662
+ ...args.slice(at)
663
+ ];
664
+ }
652
665
  /** A command line as text. */
653
666
  function commandLine(args) {
654
667
  return ["xlvane", ...args].map(quoteArg).join(" ");
@@ -805,8 +818,8 @@ function withPath(error, path) {
805
818
  }
806
819
  /**
807
820
  * For a missing file, suggests a file with a similar name in the same directory: only a name the
808
- * command can read (`accept`), and never a file that another argument of the command already
809
- * names, such as the other side of a diff (D-R08).
821
+ * command can read (`accept`), and never the path itself or a file that another argument of the
822
+ * command already names, such as the other side of a diff (D-R08, D-K04).
810
823
  */
811
824
  async function withFileHint(ctx, path, error, accept) {
812
825
  if (!(error instanceof XlvError) || error.details["code"] !== "ENOENT") return error;
@@ -817,6 +830,7 @@ async function withFileHint(ctx, path, error, accept) {
817
830
  return error;
818
831
  }
819
832
  const others = new Set(ctx.argv.filter((a) => a !== path && !a.startsWith("-")).map((a) => resolvePath(ctx, a)));
833
+ others.add(resolvePath(ctx, path));
820
834
  const candidates = names.filter((n) => accept(n) && !others.has(resolvePath(ctx, join(dirname(path), n))));
821
835
  const close = suggest(basename(path), candidates, 1);
822
836
  if (close.length === 0) return error;
@@ -844,16 +858,26 @@ async function writeOutput(ctx, path, data, options) {
844
858
  let exists = false;
845
859
  let mode = null;
846
860
  try {
847
- const info = await stat(target);
861
+ const link = await lstat(target);
848
862
  exists = true;
849
- mode = info.mode & 4095;
850
- if (!info.isFile()) throw new XlvError("XLV_E_FILE_WRITE", `"${path}" is a directory.`, {
851
- hint: "Give a file name for the output.",
852
- details: {
853
- path,
854
- code: "EISDIR"
855
- }
856
- });
863
+ const info = link.isSymbolicLink() ? await stat(target).catch(() => null) : link;
864
+ if (info !== null) {
865
+ mode = info.mode & 511;
866
+ if (info.isDirectory()) throw new XlvError("XLV_E_FILE_WRITE", `"${path}" is a directory.`, {
867
+ hint: "Give a file name for the output.",
868
+ details: {
869
+ path,
870
+ code: "EISDIR"
871
+ }
872
+ });
873
+ if (!info.isFile()) throw new XlvError("XLV_E_FILE_WRITE", `"${path}" is not a regular file.`, {
874
+ hint: "Give the name of a regular file for the output.",
875
+ details: {
876
+ path,
877
+ code: "ENOTREG"
878
+ }
879
+ });
880
+ }
857
881
  } catch (error) {
858
882
  if (error instanceof XlvError) throw error;
859
883
  }
@@ -869,13 +893,21 @@ async function writeOutput(ctx, path, data, options) {
869
893
  };
870
894
  try {
871
895
  if (exists) {
872
- const temp = join(dirname(target), `.${basename(target)}.xlvane-${String(Date.now())}.tmp`);
896
+ const random = Array.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8)), (b) => b.toString(16).padStart(2, "0")).join("");
897
+ const temp = join(dirname(target), `.${shortName(basename(target))}.xlvane-${random}.tmp`);
898
+ let created = false;
873
899
  try {
874
- await writeFile(temp, bytes, { flag: "wx" });
875
- if (mode !== null) await chmod(temp, mode);
900
+ const handle = await open(temp, "wx", mode === null ? void 0 : 384);
901
+ created = true;
902
+ try {
903
+ await handle.writeFile(bytes);
904
+ if (mode !== null) await handle.chmod(mode);
905
+ } finally {
906
+ await handle.close();
907
+ }
876
908
  await rename(temp, target);
877
909
  } catch (error) {
878
- await rm(temp, { force: true });
910
+ if (created) await rm(temp, { force: true });
879
911
  throw error;
880
912
  }
881
913
  } else await writeFile(target, bytes, { flag: "wx" });
@@ -902,6 +934,21 @@ async function writeOutput(ctx, path, data, options) {
902
934
  replaced: exists
903
935
  };
904
936
  }
937
+ /**
938
+ * The start of a file name, at most 100 bytes of UTF-8 and never cut inside a character, for the
939
+ * name of a temporary file next to it (file systems limit names in bytes, D-K03).
940
+ */
941
+ function shortName(name) {
942
+ let bytes = 0;
943
+ let end = 0;
944
+ for (const ch of name) {
945
+ const size = new TextEncoder().encode(ch).length;
946
+ if (bytes + size > 100) break;
947
+ bytes += size;
948
+ end += ch.length;
949
+ }
950
+ return name.slice(0, end);
951
+ }
905
952
  /** The line every writing command ends with; green when something was written. */
906
953
  function wroteLine(w, out) {
907
954
  if (!w.written) {
@@ -1068,6 +1115,23 @@ function formatOf(command, path, role) {
1068
1115
  [role]: path
1069
1116
  });
1070
1117
  }
1118
+ /**
1119
+ * The rows as indented JSON text. Every row repeats the header names, so a small file can ask for
1120
+ * more text than a JavaScript string can hold; that ends as a limit error, not an internal one
1121
+ * (D-J01).
1122
+ */
1123
+ function jsonText(rows) {
1124
+ try {
1125
+ return `${JSON.stringify(rows, null, 2)}\n`;
1126
+ } catch (error) {
1127
+ if (!(error instanceof RangeError)) throw error;
1128
+ throw new XlvError("XLV_E_LIMIT_EXCEEDED", "The JSON text would be longer than a JavaScript string can be.", {
1129
+ hint: "Convert part of the sheet with --range, or convert to CSV, which does not repeat the header in every row.",
1130
+ details: { limit: "maxSharedStringChars" },
1131
+ cause: error
1132
+ });
1133
+ }
1134
+ }
1071
1135
  /** The delimiter option: one character, or "tab". */
1072
1136
  function delimiterOf(ctx, command, format) {
1073
1137
  const d = str(ctx.args, "delimiter");
@@ -1091,12 +1155,7 @@ function delimiterNote(ctx, command, text, input) {
1091
1155
  const other = first.includes(";") ? ";" : first.includes(" ") ? "tab" : null;
1092
1156
  if (other === null) return;
1093
1157
  const args = ctx.argv.filter((a) => a !== "--json");
1094
- ctx.note(`note: the first line of ${input} has no "," but has ${other === "tab" ? "tabs" : "\";\""}; every line became one column. Try: ${commandLine([
1095
- command,
1096
- ...args,
1097
- "--delimiter",
1098
- other
1099
- ])}`);
1158
+ ctx.note(`note: the first line of ${input} has no "," but has ${other === "tab" ? "tabs" : "\";\""}; every line became one column. Try: ${commandLine(withOptions([command, ...args], "--delimiter", other))}`);
1100
1159
  }
1101
1160
  /** What to do with a JSON file that holds something other than an array of rows. */
1102
1161
  /**
@@ -1253,10 +1312,10 @@ const convertCommand = {
1253
1312
  ...sheet !== void 0 ? { sheet } : {},
1254
1313
  ...range !== void 0 ? { range } : {}
1255
1314
  };
1256
- if (to === "json") data = `${JSON.stringify(toJSON(workbook, {
1315
+ if (to === "json") data = jsonText(toJSON(workbook, {
1257
1316
  ...where,
1258
1317
  header: headerOption(ctx.args, "convert")
1259
- }).rows, null, 2)}\n`;
1318
+ }).rows);
1260
1319
  else data = toCSV(workbook, {
1261
1320
  ...where,
1262
1321
  delimiter: delimiterOf(ctx, "convert", to),
@@ -1682,10 +1741,10 @@ const editCommand = {
1682
1741
  render: (out) => {
1683
1742
  if (ctx.quiet && w.written) return "";
1684
1743
  const done = [
1685
- insert !== null ? `inserted ${plural(insert.count, "row")} before row ${String(insert.at)} of "${sheetOption ?? ""}"` : "",
1686
- remove !== null ? `deleted ${remove.count === 1 ? `row ${String(remove.at)}` : `rows ${String(remove.at)}–${String(remove.at + remove.count - 1)}`} of "${sheetOption ?? ""}"` : "",
1744
+ insert !== null ? `inserted ${plural(insert.count, "row")} before row ${String(insert.at)} of "${escapeControls(sheetOption ?? "")}"` : "",
1745
+ remove !== null ? `deleted ${remove.count === 1 ? `row ${String(remove.at)}` : `rows ${String(remove.at)}–${String(remove.at + remove.count - 1)}`} of "${escapeControls(sheetOption ?? "")}"` : "",
1687
1746
  parsed.length > 0 ? `set ${plural(parsed.length, "cell")}` : "",
1688
- rows.length > 0 ? `appended ${plural(rows.length, "row")} to "${appendSheet ?? ""}"` : ""
1747
+ rows.length > 0 ? `appended ${plural(rows.length, "row")} to "${escapeControls(appendSheet ?? "")}"` : ""
1689
1748
  ].filter((p) => p !== "").join(", ");
1690
1749
  return [
1691
1750
  `${done.charAt(0).toUpperCase()}${done.slice(1)}.`,
@@ -1829,6 +1888,10 @@ function renderReport(out, file, r, verbose) {
1829
1888
  lines.push(` Hyperlinks: ${plural(s.hyperlinkCount, "link")} (${shown.join(", ")}${s.hyperlinkCount > 3 ? ", …" : ""})`);
1830
1889
  }
1831
1890
  if (s.imageCount > 0) lines.push(` Images: ${plural(s.imageCount, "picture")} (${s.images.slice(0, 5).map((i) => `${i.cell ?? "absolute"} ${i.format ?? "other"}`).join(", ")}${s.imageCount > 5 ? ", …" : ""})`);
1891
+ if (s.chartCount > 0) {
1892
+ const shown = s.charts.slice(0, 5).map((c) => `${c.cell ?? "absolute"} ${escapeControls(c.types.join("+") || "empty")}${c.typesTruncated ? "+…" : ""}${c.title === null ? "" : "text" in c.title ? ` "${escapeControls(c.title.text)}"` : ` =${escapeControls(c.title.ref)}`}`);
1893
+ lines.push(` Charts: ${plural(s.chartCount, "chart")} (${shown.join(", ")}${s.chartCount > 5 ? ", …" : ""})`);
1894
+ }
1832
1895
  if (s.noteCount > 0) lines.push(` Notes: ${plural(s.noteCount, "note")} (${s.notes.slice(0, 5).join(", ")}${s.noteCount > 5 ? ", …" : ""})`);
1833
1896
  if (s.tables.length > 0) lines.push(` Tables: ${s.tables.slice(0, 5).map((table) => `${escapeControls(table.name)} ${escapeControls(table.ref)}`).join(", ")}${s.tables.length > 5 ? ", …" : ""}`);
1834
1897
  if (s.dataValidationCount > 0 || s.conditionalFormatCount > 0) {
@@ -1865,7 +1928,7 @@ const OWN_OPTIONS = /* @__PURE__ */ new Set([
1865
1928
  const mcpCommand = {
1866
1929
  name: "mcp",
1867
1930
  summary: "Start the MCP server for AI agents",
1868
- description: "Starts the Model Context Protocol server from the separate package @xlvane/mcp over stdin and stdout, so that an AI agent can inspect, read, convert and compare spreadsheets through tools instead of code. The arguments after \"mcp\" are passed to the server: --root <dir> (required, repeatable) names a directory the tools may use, --allow-write adds the tools that write files, and --max-file-size <bytes>, --max-rows <n> and --max-response-chars <n> set its limits. --json is not available, because stdout carries the protocol. If @xlvane/mcp is not installed, the command prints how to install it. The exit code is the server's: 0 when the client closed the connection, 2 for wrong arguments, 3 for a directory that cannot be used.",
1931
+ description: "Starts the Model Context Protocol server from the separate package @xlvane/mcp over stdin and stdout, so that an AI agent can inspect, read, convert and compare spreadsheets through tools instead of code. The arguments after \"mcp\" are passed to the server: --root <dir> (required, repeatable) names a directory the tools may use, --allow-write adds the tools that write files, and --max-file-size <bytes>, --max-rows <n>, --max-response-chars <n> and --max-cells <n> set its limits. --json is not available, because stdout carries the protocol. If @xlvane/mcp is not installed, the command prints how to install it. The exit code is the server's: 0 when the client closed the connection, 2 for wrong arguments, 3 for a directory that cannot be used.",
1869
1932
  usage: "[server options]",
1870
1933
  positionals: {
1871
1934
  min: 0,
@@ -2066,9 +2129,23 @@ function jsonError(path, cause) {
2066
2129
  cause
2067
2130
  });
2068
2131
  }
2132
+ async function isLink(path) {
2133
+ try {
2134
+ return (await lstat(path)).isSymbolicLink();
2135
+ } catch {
2136
+ return false;
2137
+ }
2138
+ }
2069
2139
  async function run$1(ctx) {
2070
2140
  const dir = ctx.args.positionals[0] ?? ".";
2071
2141
  const pkgPath = join(dir, "package.json");
2142
+ if (await isLink(resolvePath(ctx, pkgPath))) throw new XlvError("XLV_E_FILE_READ", `"${pkgPath}" is a symbolic link; migrate does not follow links.`, {
2143
+ hint: "Run migrate in the directory that holds the real package.json.",
2144
+ details: {
2145
+ path: pkgPath,
2146
+ code: "ELOOP"
2147
+ }
2148
+ });
2072
2149
  const text = await readText(ctx, pkgPath);
2073
2150
  let pkg;
2074
2151
  try {
@@ -2138,17 +2215,22 @@ async function run$1(ctx) {
2138
2215
  else if (alreadyMigrated) lines.push(`${pkgPath} already uses ${ALIAS} for "exceljs".`);
2139
2216
  else {
2140
2217
  lines.push(`${out.bold("Change in")} ${pkgPath}:`);
2141
- for (const c of changes) lines.push(` ${c.section}: ${out.red(`- "exceljs": "${c.from}"`)}`, ` ${" ".repeat(c.section.length)} ${out.green(`+ "exceljs": "${c.to}"`)}`);
2218
+ for (const c of changes) lines.push(` ${c.section}: ${out.red(`- "exceljs": "${escapeControls(c.from)}"`)}`, ` ${" ".repeat(c.section.length)} ${out.green(`+ "exceljs": "${escapeControls(c.to)}"`)}`);
2142
2219
  }
2143
- lines.push(`Node.js: require('exceljs') of @xlvane/compat needs Node.js 22.12 or later (package.json engines.node: ${typeof engines === "string" ? `"${engines}"` : "not set"}).`);
2220
+ lines.push(`Node.js: require('exceljs') of @xlvane/compat needs Node.js 22.12 or later (package.json engines.node: ${typeof engines === "string" ? `"${escapeControls(engines)}"` : "not set"}).`);
2144
2221
  lines.push("", `Scanned ${plural(files.length, "source file")}${limited ? ` (stopped at ${String(MAX_FILES)})` : ""} for exceljs API that @xlvane/compat does not fully support:`);
2145
2222
  if (findings.length === 0) lines.push(" none found.");
2146
- for (const f of findings.slice(0, 200)) lines.push(` ${f.status === "partly" ? out.yellow("partly ") : out.red("not supported")} ${f.file}:${String(f.line)}:${String(f.column)} ${f.api}`);
2223
+ for (const f of findings.slice(0, 200)) lines.push(` ${f.status === "partly" ? out.yellow("partly ") : out.red("not supported")} ${escapeControls(f.file)}:${String(f.line)}:${String(f.column)} ${escapeControls(f.api)}`);
2147
2224
  if (findings.length > 200) lines.push(out.dim(` … ${String(findings.length - 200)} more; use --json for all`));
2148
2225
  lines.push(" Details: docs/exceljs-compat.md. Unsupported calls throw XLV_E_NOT_SUPPORTED at run time.");
2149
2226
  lines.push("");
2150
2227
  if (write) lines.push(wroteLine(write));
2151
- else if (changes.length > 0) lines.push(`Nothing was written. Run "${commandLine([
2228
+ else if (changes.length > 0) lines.push(`Nothing was written. Run "${commandLine(dir.startsWith("-") ? [
2229
+ "migrate",
2230
+ "--force",
2231
+ "--",
2232
+ dir
2233
+ ] : [
2152
2234
  "migrate",
2153
2235
  ...dir === "." ? [] : [dir],
2154
2236
  "--force"
@@ -2300,7 +2382,7 @@ const readCommand = {
2300
2382
  result,
2301
2383
  exitCode: 0,
2302
2384
  render: (out) => {
2303
- if (table.columns.length === 0) return `Sheet "${table.sheet}" has no data${table.range ? ` in ${table.range}` : ""}.`;
2385
+ if (table.columns.length === 0) return `Sheet "${escapeControls(table.sheet)}" has no data${table.range ? ` in ${table.range}` : ""}.`;
2304
2386
  const numeric = table.columns.map((c) => shown.length > 0 && shown.every((row) => row[c.key] === null || typeof row[c.key] === "number") ? "right" : "left");
2305
2387
  return out.table(table.columns.map((c) => c.header ?? c.key), shown.map((row) => table.columns.map((c) => showValue(row[c.key]))), "", numeric);
2306
2388
  }
@@ -2410,6 +2492,8 @@ const COMMANDS = [
2410
2492
  const problems = [];
2411
2493
  let sheetName = null;
2412
2494
  let rowsChecked = 0;
2495
+ const named = str(ctx.args, "sheet");
2496
+ if (named !== void 0) workbook.getSheet(named);
2413
2497
  if (rules.length > 0) {
2414
2498
  const sheet = str(ctx.args, "sheet");
2415
2499
  const table = readTable(workbook, {
@@ -2481,7 +2565,7 @@ const COMMANDS = [
2481
2565
  lines.push("");
2482
2566
  }
2483
2567
  if (warnings.length > 0) lines.push(...warningsBlock(out, warnings), "");
2484
- const checked = sheetName !== null ? `, ${plural(rowsChecked, "row")} of sheet "${sheetName}" checked against ${plural(rules.length, "column rule")}` : "";
2568
+ const checked = sheetName !== null ? `, ${plural(rowsChecked, "row")} of sheet "${escapeControls(sheetName)}" checked against ${plural(rules.length, "column rule")}` : "";
2485
2569
  if (ok && warnings.length > 0) lines.push(`${out.yellow(`OK with ${plural(warnings.length, "warning")}`)}: ${file}${checked}. Use --strict to fail on warnings.`);
2486
2570
  else if (ok) lines.push(`${out.green("OK")}: ${file}${checked}.`);
2487
2571
  else lines.push(`${out.red("Failed")}: ${file}${checked}${strict && problems.length === 0 ? " (warnings count with --strict)" : ""}.`);
@@ -2658,11 +2742,7 @@ function suggestCommand(error, first, rest, spec) {
2658
2742
  }
2659
2743
  }
2660
2744
  if (spec === void 0) return null;
2661
- if (error.code === "XLV_E_FILE_EXISTS") return commandLine([
2662
- spec.name,
2663
- ...rest,
2664
- "--force"
2665
- ]);
2745
+ if (error.code === "XLV_E_FILE_EXISTS") return commandLine(withOptions([spec.name, ...rest], "--force"));
2666
2746
  if (error.code === "XLV_E_SHEET_NOT_FOUND" && Array.isArray(d["available"]) && typeof error.location.sheet === "string") {
2667
2747
  const close = suggest(error.location.sheet, d["available"], 1);
2668
2748
  const fixed = close.length > 0 ? withSheetName(rest, error.location.sheet, close[0]) : null;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as InspectOptions, $n as verifyPassword, $t as DataValidationOperator, A as toJSON, An as Hyperlink, At as Workbook, B as FormulaResult, Bn as PrintErrors, Bt as CfValue, C as TableRow, Cn as ImageAnchorInput, Ct as Sheet, D as fromJSON, Dn as SheetImage, Dt as SheetState, E as WriteTableOptions, En as ImagePositionInput, Et as SheetProperties, F as CellInput, Fn as PageOrder, Ft as TableDefinitionColumn, G as SheetPropertiesOptions, Gn as SheetProtectionOptions, Gt as ConditionalFormatRule, H as NewSheet, Hn as PrintTitles, Ht as CfValueType, I as ColumnOptions, In as PageOrientation, It as TableInput, J as WriteWorkbookOptions, Jn as HashPasswordOptions, Jt as DataBarAxisPosition, K as SheetViewOptions, Kn as WorkbookProtection, Kt as ConditionalFormatRuleInput, L as CreateWorkbookOptions, Ln as PageSetup, Lt as TableStyleOptions, M as StandardSchemaV1, Mn as HeaderFooter, Mt as WorkbookView, N as AddSheetOptions, Nn as PageBreaks, Nt as TableColumnInput, O as readTable, On as WorkbookImage, Ot as SheetView, P as CalculationOptions, Pn as PageMargins, Pt as TableDefinition, Q as InspectDefinedName, Qn as legacyPasswordHash, Qt as DataValidationImeMode, R as ERROR_VALUES, Rn as PageSetupOptions, Rt as TotalsRowFunction, S as TableResult, Sn as ImageAnchor, St as RowInfo, T as ToJSONOptions, Tn as ImageOptions, Tt as SheetLayout, U as NewWorkbook, Un as SheetProtection, Ut as ConditionalFormat, V as FreezeOptions, Vn as PrintOptions, Vt as CfValueInput, W as RowOptions, Wn as SheetProtectionFlags, Wt as ConditionalFormatOperator, X as writeWorkbook, Xn as PasswordVerifier, Xt as DataValidation, Y as createWorkbook, Yn as PasswordHash, Yt as DataBarDirection, Z as InspectColumn, Zn as hashPassword, Zt as DataValidationErrorStyle, _ as JsonTable, _n as ReadingOrder, _r as DefinedNameOptions, _t as DateSystem, a as WriteWorkbookStreamOptions, an as BorderEdge, ar as XlvDetails, at as ReadOptions, b as TableColumnSpec, bn as VerticalAlignment, bt as FrozenPanes, c as OpenWorkbookStreamOptions, cn as CellStyle, cr as XlvErrorInit, ct as NoteBox, d as StreamRow, dn as FontScheme, dr as XlvWarning, dt as NoteMargins, en as DataValidationRule, er as ShaAlgorithm, et as InspectReport, f as StreamSheetInfo, fn as FontStyle, fr as XlvWarningCode, ft as Cell, g as readTableStream, gn as ProtectionStyle, gr as DefinedName, gt as Conformance, h as openWorkbookStream, hn as PatternType, hr as ReadLimits, ht as ColumnInfo, i as WriteTableStreamOptions, in as AlignmentStyle, ir as XlvDetailValue, it as inspect, j as writeTable, jn as HyperlinkTarget, jt as WorkbookFormat, k as readTableAsync, kn as imageFormat, kt as SplitPanes, l as ReadTableStreamOptions, ln as Color, lr as XlvErrorJSON, lt as NoteBoxInput, m as WorkbookStream, mn as HorizontalAlignment, mr as DEFAULT_READ_LIMITS, mt as CellValue, n as StreamRowInput, nn as IconSetType, nr as ERROR_CODES, nt as InspectTypeCounts, o as writeTableStream, on as BorderLineStyle, or as XlvError, ot as readWorkbook, p as TableStreamItem, pn as GradientFill, pr as isXlvError, pt as CellType, q as WorkbookViewOptions, qn as WorkbookProtectionOptions, qt as ConditionalFormatType, r as StreamSheetSpec, rn as TimePeriod, rr as WARNING_CODES, rt as InspectValue, s as writeWorkbookStream, sn as BorderStyle, sr as XlvErrorCode, st as Note, t as StreamDefinedName, tn as DataValidationType, tr as WorkbookProperties, tt as InspectSheet, u as STREAM_LIMIT_DEFAULTS, un as FillStyle, ur as XlvLocation, ut as NoteInput, v as ReadTableOptions, vn as UnderlineStyle, vt as Formula, w as TableValue, wn as ImageFormat, wt as SheetKind, x as TableIssue, xn as AnchorMarker, xt as RichTextRun, y as TableColumn, yn as VerticalAlignRun, yt as FormulaKind, z as ErrorValue, zn as PrintCellComments, zt as CfIcon } from "./stream-write-BVaFkgwO.js";
1
+ import { $ as InspectOptions, $n as SheetProtection, $t as DataValidationOperator, A as toJSON, An as SheetChart, At as Workbook, B as FormulaResult, Bn as Hyperlink, Bt as CfValue, C as TableRow, Cn as ChartInput, Cr as isXlvError, Ct as Sheet, D as fromJSON, Dn as ChartSeriesInput, Dr as DefinedNameOptions, Dt as SheetState, E as WriteTableOptions, En as ChartSeries, Er as DefinedName, Et as SheetProperties, F as CellInput, Fn as ImageOptions, Ft as TableDefinitionColumn, G as SheetPropertiesOptions, Gn as PageOrder, Gt as ConditionalFormatRule, H as NewSheet, Hn as HeaderFooter, Ht as CfValueType, I as ColumnOptions, In as ImagePositionInput, It as TableInput, J as WriteWorkbookOptions, Jn as PageSetupOptions, Jt as DataBarAxisPosition, K as SheetViewOptions, Kn as PageOrientation, Kt as ConditionalFormatRuleInput, L as CreateWorkbookOptions, Ln as SheetImage, Lt as TableStyleOptions, M as StandardSchemaV1, Mn as ImageAnchor, Mt as WorkbookView, N as AddSheetOptions, Nn as ImageAnchorInput, Nt as TableColumnInput, O as readTable, On as ChartText, Ot as SheetView, P as CalculationOptions, Pn as ImageFormat, Pt as TableDefinition, Q as InspectDefinedName, Qn as PrintTitles, Qt as DataValidationImeMode, R as ERROR_VALUES, Rn as WorkbookImage, Rt as TotalsRowFunction, S as TableResult, Sn as ChartAxisInput, Sr as XlvWarningCode, St as RowInfo, T as ToJSONOptions, Tn as ChartLegendPosition, Tr as ReadLimits, Tt as SheetLayout, U as NewWorkbook, Un as PageBreaks, Ut as ConditionalFormat, V as FreezeOptions, Vn as HyperlinkTarget, Vt as CfValueInput, W as RowOptions, Wn as PageMargins, Wt as ConditionalFormatOperator, X as writeWorkbook, Xn as PrintErrors, Xt as DataValidation, Y as createWorkbook, Yn as PrintCellComments, Yt as DataBarDirection, Z as InspectColumn, Zn as PrintOptions, Zt as DataValidationErrorStyle, _ as JsonTable, _n as ReadingOrder, _r as XlvErrorCode, _t as DateSystem, a as WriteWorkbookStreamOptions, an as BorderEdge, ar as PasswordHash, at as ReadOptions, b as TableColumnSpec, bn as VerticalAlignment, br as XlvLocation, bt as FrozenPanes, c as OpenWorkbookStreamOptions, cn as CellStyle, cr as legacyPasswordHash, ct as NoteBox, d as StreamRow, dn as FontScheme, dr as WorkbookProperties, dt as NoteMargins, en as DataValidationRule, er as SheetProtectionFlags, et as InspectReport, f as StreamSheetInfo, fn as FontStyle, fr as ERROR_CODES, ft as Cell, g as readTableStream, gn as ProtectionStyle, gr as XlvError, gt as Conformance, h as openWorkbookStream, hn as PatternType, hr as XlvDetails, ht as ColumnInfo, i as WriteTableStreamOptions, in as AlignmentStyle, ir as HashPasswordOptions, it as inspect, j as writeTable, jn as AnchorMarker, jt as WorkbookFormat, k as readTableAsync, kn as ChartType, kt as SplitPanes, l as ReadTableStreamOptions, ln as Color, lr as verifyPassword, lt as NoteBoxInput, m as WorkbookStream, mn as HorizontalAlignment, mr as XlvDetailValue, mt as CellValue, n as StreamRowInput, nn as IconSetType, nr as WorkbookProtection, nt as InspectTypeCounts, o as writeTableStream, on as BorderLineStyle, or as PasswordVerifier, ot as readWorkbook, p as TableStreamItem, pn as GradientFill, pr as WARNING_CODES, pt as CellType, q as WorkbookViewOptions, qn as PageSetup, qt as ConditionalFormatType, r as StreamSheetSpec, rn as TimePeriod, rr as WorkbookProtectionOptions, rt as InspectValue, s as writeWorkbookStream, sn as BorderStyle, sr as hashPassword, st as Note, t as StreamDefinedName, tn as DataValidationType, tr as SheetProtectionOptions, tt as InspectSheet, u as STREAM_LIMIT_DEFAULTS, un as FillStyle, ur as ShaAlgorithm, ut as NoteInput, v as ReadTableOptions, vn as UnderlineStyle, vr as XlvErrorInit, vt as Formula, w as TableValue, wn as ChartKind, wr as DEFAULT_READ_LIMITS, wt as SheetKind, x as TableIssue, xn as ChartAxis, xr as XlvWarning, xt as RichTextRun, y as TableColumn, yn as VerticalAlignRun, yr as XlvErrorJSON, yt as FormulaKind, z as ErrorValue, zn as imageFormat, zt as CfIcon } from "./stream-write-XnCyLdOy.js";
2
2
  /**
3
3
  * Options for {@link toCSV}.
4
4
  *
@@ -14,7 +14,11 @@ interface ToCSVOptions {
14
14
  readonly sheet?: string;
15
15
  /** Only this range, for example `"A1:D100"`. Default: from A1 to the last used row and column. */
16
16
  readonly range?: string;
17
- /** Field separator: one character other than `"`, CR or LF. Default `","`. */
17
+ /**
18
+ * Field separator: one character other than `"`, CR or LF. Default `","`. While formulas are
19
+ * escaped (`formulaInjection: "escape"`), `=`, `+`, `-`, `@` and their full-width and small forms
20
+ * are refused, because a line whose first field is empty would start with them.
21
+ */
18
22
  readonly delimiter?: string;
19
23
  /** Line ending. Default `"\r\n"` (RFC 4180). */
20
24
  readonly newline?: "\r\n" | "\n";
@@ -22,7 +26,8 @@ interface ToCSVOptions {
22
26
  readonly bom?: boolean;
23
27
  /**
24
28
  * `"escape"` (default): text cells that start with `=`, `+`, `-`, `@`, their full-width or small
25
- * forms, or a control character (tab, CR, LF, NUL, …) get a leading `'` and quotes, so
29
+ * forms, a control character (tab, CR, LF, NUL, …) or a byte order mark (U+FEFF, U+FFFE, which
30
+ * spreadsheet applications drop at the start of a file) get a leading `'` and quotes, so
26
31
  * spreadsheet applications show them as text instead of running them as formulas (OWASP CSV
27
32
  * Injection). `"off"`: write them as is.
28
33
  */
@@ -33,8 +38,9 @@ interface ToCSVOptions {
33
38
  readonly onWarning?: (warning: XlvWarning) => void;
34
39
  }
35
40
  /**
36
- * Converts a sheet to CSV text. Fields that contain the delimiter, a quote, CR or LF are
37
- * enclosed in quotes, and quotes are doubled (RFC 4180). Numbers are written in JavaScript's
41
+ * Converts a sheet to CSV text. Fields that contain the delimiter, a quote, CR, LF, or one of
42
+ * the common separators `,`, `;`, tab and `|` (whatever the delimiter, CSV-14) are enclosed in
43
+ * quotes, and quotes are doubled (RFC 4180). Numbers are written in JavaScript's
38
44
  * shortest form, booleans as `TRUE`/`FALSE`, dates as ISO 8601, error cells as their code and
39
45
  * formula cells as their cached value. By default, text that a spreadsheet would run as a
40
46
  * formula is escaped (`formulaInjection: "escape"`).
@@ -266,7 +272,12 @@ interface CellEdit {
266
272
  * ```
267
273
  */
268
274
  interface EditOptions {
269
- /** Read limits for the input file; see `ReadLimits`. */
275
+ /**
276
+ * Read limits for the input file; see `ReadLimits`. For `editCells` and `appendRows`,
277
+ * `maxCells` also bounds the edit: the cells of the edited sheets plus the new cells must not
278
+ * exceed it, and one call writes at most 1 000 000 cells unless `maxCells` is given, in which
279
+ * case it writes at most `maxCells` (`XLV_E_LIMIT_EXCEEDED`, `details.limit` `"maxCells"`).
280
+ */
270
281
  readonly limits?: Partial<ReadLimits>;
271
282
  }
272
283
  /**
@@ -329,10 +340,14 @@ export declare function editCells(input: Uint8Array | ArrayBuffer, edits: readon
329
340
  * Appends rows below the last used row of a sheet (the rows of merged ranges count as used) in
330
341
  * an existing XLSX file, editing the file surgically like {@link editCells}. Object rows are
331
342
  * placed under the matching header columns (unknown keys throw `XLV_E_INVALID_ROWS` with
332
- * suggestions); array rows start at the first used column. A table whose last row is the sheet's last used row grows to include the new
333
- * rows (its range and filter); if that table has a totals row the call is refused with
343
+ * suggestions). Array rows start at the first used column, or, when the new rows extend a table
344
+ * (one that reaches the last used row), at that table's first column; when several such tables
345
+ * start in different columns the call is refused with `XLV_E_INVALID_ARGUMENT` (pass object rows
346
+ * instead). A table whose last row is the sheet's last used row grows to include the new rows
347
+ * (its range and filter); if that table has a totals row the call is refused with
334
348
  * `XLV_E_NOT_SUPPORTED` and a hint to insert the rows above the totals row with `insertRows`.
335
- * Calculated columns are not filled in the new rows.
349
+ * Calculated columns are not filled in the new rows. One call writes at most 1 000 000 cells
350
+ * unless `limits.maxCells` is given (see `EditOptions`).
336
351
  *
337
352
  * @example
338
353
  * ```ts
@@ -343,6 +358,9 @@ export declare function editCells(input: Uint8Array | ArrayBuffer, edits: readon
343
358
  * { Customer: 'Alice', Total: 120.5 },
344
359
  * { Customer: 'Bob', Total: 80 },
345
360
  * ]);
361
+ * // Array rows that extend a table start at the table's first column.
362
+ * const { bytes: more } = appendRows(updated, 'Orders', [['Carol', 64]]);
363
+ * console.log(more.length);
346
364
  * ```
347
365
  */
348
366
  export declare function appendRows(input: Uint8Array | ArrayBuffer, sheet: string, rows: readonly (readonly CellInput[])[] | readonly Readonly<Record<string, CellInput>>[], options?: AppendRowsOptions): EditResult;
@@ -367,6 +385,8 @@ interface InsertRowsOptions extends EditOptions {
367
385
  /**
368
386
  * Formatting of the new rows: `'above'` copies the row format, height and cell formats of the row
369
387
  * above, `'below'` those of the row that is pushed down, `'none'` (default) leaves them empty.
388
+ * The copied cell formats are cells that count against `limits.maxCells` with the cells of the
389
+ * workbook (`XLV_E_LIMIT_EXCEEDED` above it).
370
390
  */
371
391
  readonly style?: "above" | "below" | "none";
372
392
  }
@@ -403,7 +423,9 @@ interface DeleteRowsOptions extends EditOptions {
403
423
  * custom sheet views, other extended content that refers to cells (sparklines, slicers) and
404
424
  * other related parts xlvane does not know; charts or pivot tables anywhere in the workbook that
405
425
  * may refer to the sheet; data tables; an array formula the change would split; and formulas it
406
- * cannot rewrite safely (docs/facts/ref-adjust.md).
426
+ * cannot rewrite safely (docs/facts/ref-adjust.md). Rows formatted with `style` add a formatted
427
+ * cell for each formatted cell of the row they copy; when the workbook would then hold more cells
428
+ * than `limits.maxCells`, the call throws `XLV_E_LIMIT_EXCEEDED` before building them.
407
429
  *
408
430
  * @example
409
431
  * ```ts
@@ -517,4 +539,4 @@ export declare function adjustRange(sqref: string, change: ReferenceChange): str
517
539
  * ```
518
540
  */
519
541
  export declare function shiftFormula(text: string, dRow: number, dCol: number): string | null;
520
- export { type AddSheetOptions, type AlignmentStyle, type AnchorMarker, type AppendRowsOptions, type BorderEdge, type BorderLineStyle, type BorderStyle, type CalculationOptions, type Cell, type CellChange, type CellEdit, type CellInput, type CellStyle, type CellType, type CellValue, type CfIcon, type CfValue, type CfValueInput, type CfValueType, type Color, type ColumnInfo, type ColumnOptions, type ConditionalFormat, type ConditionalFormatOperator, type ConditionalFormatRule, type ConditionalFormatRuleInput, type ConditionalFormatType, type Conformance, type CreateWorkbookOptions, DEFAULT_READ_LIMITS, type DataBarAxisPosition, type DataBarDirection, type DataValidation, type DataValidationErrorStyle, type DataValidationImeMode, type DataValidationOperator, type DataValidationRule, type DataValidationType, type DateSystem, type DefinedName, type DefinedNameOptions, type DeleteRowsOptions, type DiffCell, type DiffOptions, ERROR_CODES, ERROR_VALUES, type EditOptions, type EditResult, type ErrorValue, type FillStyle, type FontScheme, type FontStyle, type Formula, type FormulaKind, type FormulaResult, type FreezeOptions, type FromCSVOptions, type FrozenPanes, type GradientFill, type HashPasswordOptions, type HeaderFooter, type HorizontalAlignment, type Hyperlink, type HyperlinkTarget, type IconSetType, type ImageAnchor, type ImageAnchorInput, type ImageFormat, type ImageOptions, type ImagePositionInput, type InsertRowsOptions, type InspectColumn, type InspectDefinedName, type InspectOptions, type InspectReport, type InspectSheet, type InspectTypeCounts, type InspectValue, type JsonTable, NewSheet, NewWorkbook, type Note, type NoteBox, type NoteBoxInput, type NoteInput, type NoteMargins, type OpenWorkbookStreamOptions, type PageBreaks, type PageMargins, type PageOrder, type PageOrientation, type PageSetup, type PageSetupOptions, type PasswordHash, type PasswordVerifier, type PatternType, type PrintCellComments, type PrintErrors, type PrintOptions, type PrintTitles, type ProtectionStyle, type ReadLimits, type ReadOptions, type ReadTableOptions, type ReadTableStreamOptions, type ReadingOrder, type ReferenceChange, type RichTextRun, type RowInfo, type RowOptions, STREAM_LIMIT_DEFAULTS, type ShaAlgorithm, type Sheet, type SheetDiff, type SheetImage, type SheetKind, type SheetLayout, type SheetProperties, type SheetPropertiesOptions, type SheetProtection, type SheetProtectionFlags, type SheetProtectionOptions, type SheetState, type SheetView, type SheetViewOptions, type SplitPanes, type StandardSchemaV1, type StreamDefinedName, type StreamRow, type StreamRowInput, type StreamSheetInfo, type StreamSheetSpec, type TableColumn, type TableColumnInput, type TableColumnSpec, type TableDefinition, type TableDefinitionColumn, type TableInput, type TableIssue, type TableResult, type TableRow, type TableStreamItem, type TableStyleOptions, type TableValue, type TimePeriod, type ToCSVOptions, type ToJSONOptions, type TotalsRowFunction, type UnderlineStyle, type VerticalAlignRun, type VerticalAlignment, WARNING_CODES, type Workbook, type WorkbookDiff, type WorkbookFormat, type WorkbookImage, type WorkbookProperties, type WorkbookProtection, type WorkbookProtectionOptions, type WorkbookStream, type WorkbookView, type WorkbookViewOptions, type WriteTableOptions, type WriteTableStreamOptions, type WriteWorkbookOptions, type WriteWorkbookStreamOptions, type XlvDetailValue, type XlvDetails, XlvError, type XlvErrorCode, type XlvErrorInit, type XlvErrorJSON, type XlvLocation, type XlvWarning, type XlvWarningCode, createWorkbook, fromJSON, hashPassword, imageFormat, inspect, isXlvError, legacyPasswordHash, openWorkbookStream, readTable, readTableAsync, readTableStream, readWorkbook, toJSON, verifyPassword, writeTable, writeTableStream, writeWorkbook, writeWorkbookStream };
542
+ export { type AddSheetOptions, type AlignmentStyle, type AnchorMarker, type AppendRowsOptions, type BorderEdge, type BorderLineStyle, type BorderStyle, type CalculationOptions, type Cell, type CellChange, type CellEdit, type CellInput, type CellStyle, type CellType, type CellValue, type CfIcon, type CfValue, type CfValueInput, type CfValueType, type ChartAxis, type ChartAxisInput, type ChartInput, type ChartKind, type ChartLegendPosition, type ChartSeries, type ChartSeriesInput, type ChartText, type ChartType, type Color, type ColumnInfo, type ColumnOptions, type ConditionalFormat, type ConditionalFormatOperator, type ConditionalFormatRule, type ConditionalFormatRuleInput, type ConditionalFormatType, type Conformance, type CreateWorkbookOptions, DEFAULT_READ_LIMITS, type DataBarAxisPosition, type DataBarDirection, type DataValidation, type DataValidationErrorStyle, type DataValidationImeMode, type DataValidationOperator, type DataValidationRule, type DataValidationType, type DateSystem, type DefinedName, type DefinedNameOptions, type DeleteRowsOptions, type DiffCell, type DiffOptions, ERROR_CODES, ERROR_VALUES, type EditOptions, type EditResult, type ErrorValue, type FillStyle, type FontScheme, type FontStyle, type Formula, type FormulaKind, type FormulaResult, type FreezeOptions, type FromCSVOptions, type FrozenPanes, type GradientFill, type HashPasswordOptions, type HeaderFooter, type HorizontalAlignment, type Hyperlink, type HyperlinkTarget, type IconSetType, type ImageAnchor, type ImageAnchorInput, type ImageFormat, type ImageOptions, type ImagePositionInput, type InsertRowsOptions, type InspectColumn, type InspectDefinedName, type InspectOptions, type InspectReport, type InspectSheet, type InspectTypeCounts, type InspectValue, type JsonTable, NewSheet, NewWorkbook, type Note, type NoteBox, type NoteBoxInput, type NoteInput, type NoteMargins, type OpenWorkbookStreamOptions, type PageBreaks, type PageMargins, type PageOrder, type PageOrientation, type PageSetup, type PageSetupOptions, type PasswordHash, type PasswordVerifier, type PatternType, type PrintCellComments, type PrintErrors, type PrintOptions, type PrintTitles, type ProtectionStyle, type ReadLimits, type ReadOptions, type ReadTableOptions, type ReadTableStreamOptions, type ReadingOrder, type ReferenceChange, type RichTextRun, type RowInfo, type RowOptions, STREAM_LIMIT_DEFAULTS, type ShaAlgorithm, type Sheet, type SheetChart, type SheetDiff, type SheetImage, type SheetKind, type SheetLayout, type SheetProperties, type SheetPropertiesOptions, type SheetProtection, type SheetProtectionFlags, type SheetProtectionOptions, type SheetState, type SheetView, type SheetViewOptions, type SplitPanes, type StandardSchemaV1, type StreamDefinedName, type StreamRow, type StreamRowInput, type StreamSheetInfo, type StreamSheetSpec, type TableColumn, type TableColumnInput, type TableColumnSpec, type TableDefinition, type TableDefinitionColumn, type TableInput, type TableIssue, type TableResult, type TableRow, type TableStreamItem, type TableStyleOptions, type TableValue, type TimePeriod, type ToCSVOptions, type ToJSONOptions, type TotalsRowFunction, type UnderlineStyle, type VerticalAlignRun, type VerticalAlignment, WARNING_CODES, type Workbook, type WorkbookDiff, type WorkbookFormat, type WorkbookImage, type WorkbookProperties, type WorkbookProtection, type WorkbookProtectionOptions, type WorkbookStream, type WorkbookView, type WorkbookViewOptions, type WriteTableOptions, type WriteTableStreamOptions, type WriteWorkbookOptions, type WriteWorkbookStreamOptions, type XlvDetailValue, type XlvDetails, XlvError, type XlvErrorCode, type XlvErrorInit, type XlvErrorJSON, type XlvLocation, type XlvWarning, type XlvWarningCode, createWorkbook, fromJSON, hashPassword, imageFormat, inspect, isXlvError, legacyPasswordHash, openWorkbookStream, readTable, readTableAsync, readTableStream, readWorkbook, toJSON, verifyPassword, writeTable, writeTableStream, writeWorkbook, writeWorkbookStream };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Dn as WARNING_CODES, En as ERROR_CODES, Gt as hashPassword, Kt as legacyPasswordHash, On as XlvError, Qt as imageFormat, Rt as DEFAULT_READ_LIMITS, b as readWorkbook, c as NewSheet, f as createWorkbook, g as inspect, kn as isXlvError, l as NewWorkbook, m as writeWorkbook, qt as verifyPassword, s as ERROR_VALUES, z as shiftFormula } from "./options-Ba1iWD-Z.js";
2
- import { a as appendRows, c as fromCSV, d as fromJSON, g as writeTable, h as toJSON, i as adjustRange, m as readTableAsync, n as insertRows, o as editCells, p as readTable, r as adjustFormula, s as diff, t as deleteRows, u as toCSV } from "./rows-IWKIxHt9.js";
3
- import { a as readTableStream, i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS, t as writeTableStream } from "./stream-write-BGuUjlXo.js";
1
+ import { Bn as isXlvError, Gt as hashPassword, Kt as legacyPasswordHash, Ln as ERROR_CODES, Lt as DEFAULT_READ_LIMITS, Qt as imageFormat, Rn as WARNING_CODES, _ as inspect, c as NewSheet, dn as shiftFormula, f as createWorkbook, l as NewWorkbook, m as writeWorkbook, qt as verifyPassword, s as ERROR_VALUES, x as readWorkbook, zn as XlvError } from "./options-Cxt-LJE4.js";
2
+ import { a as appendRows, c as fromCSV, d as fromJSON, g as writeTable, h as toJSON, i as adjustRange, m as readTableAsync, n as insertRows, o as editCells, p as readTable, r as adjustFormula, s as diff, t as deleteRows, u as toCSV } from "./rows-DltZ-hiW.js";
3
+ import { a as readTableStream, i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS, t as writeTableStream } from "./stream-write-D573ewpr.js";
4
4
  export { DEFAULT_READ_LIMITS, ERROR_CODES, ERROR_VALUES, NewSheet, NewWorkbook, STREAM_LIMIT_DEFAULTS, WARNING_CODES, XlvError, adjustFormula, adjustRange, appendRows, createWorkbook, deleteRows, diff, editCells, fromCSV, fromJSON, hashPassword, imageFormat, insertRows, inspect, isXlvError, legacyPasswordHash, openWorkbookStream, readTable, readTableAsync, readTableStream, readWorkbook, shiftFormula, toCSV, toJSON, verifyPassword, writeTable, writeTableStream, writeWorkbook, writeWorkbookStream };
@@ -1,4 +1,4 @@
1
- import { Bt as resolveLimits, On as XlvError } from "./options-Ba1iWD-Z.js";
1
+ import { Bt as resolveLimits, zn as XlvError } from "./options-Cxt-LJE4.js";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  // src/util/node-files.ts
4
4
  /** The path as shown in messages. */
@@ -12,7 +12,7 @@ async function checkFile(path, limits) {
12
12
  const info = await stat(path);
13
13
  if (!info.isFile()) throw new XlvError("XLV_E_FILE_READ", `"${shown}" is not a file.`, {
14
14
  location: {},
15
- hint: "Pass the path of an .xlsx file, not a directory.",
15
+ hint: info.isDirectory() ? "Pass the path of an .xlsx file, not a directory." : "Pass the path of an .xlsx file, not a pipe, device or other special file.",
16
16
  details: { path: shown }
17
17
  });
18
18
  size = info.size;
package/dist/node.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as InspectOptions, At as Workbook, J as WriteWorkbookOptions, U as NewWorkbook, a as WriteWorkbookStreamOptions, at as ReadOptions, c as OpenWorkbookStreamOptions, et as InspectReport, m as WorkbookStream } from "./stream-write-BVaFkgwO.js";
1
+ import { $ as InspectOptions, At as Workbook, J as WriteWorkbookOptions, U as NewWorkbook, a as WriteWorkbookStreamOptions, at as ReadOptions, c as OpenWorkbookStreamOptions, et as InspectReport, m as WorkbookStream } from "./stream-write-XnCyLdOy.js";
2
2
  /**
3
3
  * Reads an XLSX file from disk. Node.js only; in other runtimes pass the bytes to
4
4
  * `readWorkbook` from `xlvane`. The file size is checked against `limits.maxInputBytes`
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Bt as resolveLimits, On as XlvError, b as readWorkbook, g as inspect, m as writeWorkbook } from "./options-Ba1iWD-Z.js";
2
- import { i as readInput, n as describePath, r as fileError, t as checkFile } from "./node-files-CDb6TyZb.js";
3
- import { i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS } from "./stream-write-BGuUjlXo.js";
1
+ import { Bt as resolveLimits, _ as inspect, m as writeWorkbook, x as readWorkbook, zn as XlvError } from "./options-Cxt-LJE4.js";
2
+ import { i as readInput, n as describePath, r as fileError, t as checkFile } from "./node-files-SVg17pky.js";
3
+ import { i as openWorkbookStream, n as writeWorkbookStream, r as STREAM_LIMIT_DEFAULTS } from "./stream-write-D573ewpr.js";
4
4
  import { openAsBlob } from "node:fs";
5
5
  import { open, unlink, writeFile } from "node:fs/promises";
6
6
  // src/node.ts