@xlsxflow/core 1.1.2 → 1.1.3

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/CHANGELOG.md CHANGED
@@ -1,108 +1,122 @@
1
- # Changelog
2
-
3
- ## 1.1.2
4
-
5
- ### Fixed
6
-
7
- - `SheetWriter` throws an error for a row longer than 16,384 cells or past row 1,048,576, instead of writing a file Excel reports as damaged.
8
- - `SheetEditor.setCells`: a date in a cell without a date format gets `yyyy-mm-dd` (or `yyyy-mm-dd hh:mm:ss` with a time), as in `SheetWriter`, instead of showing as a serial number.
9
- - `OdsWriter` stores formula results, so readers that don't recalculate show them.
10
- - `sheetToJson` keeps every column when headers repeat: the second `Name` becomes `Name_2`.
11
- - `parseCsv` returns `null` for empty unquoted fields with `{ convert: false }` too.
12
-
13
- ### Added
14
-
15
- - `StylePatcher` is exported, for `@xlsxflow/pro`.
16
-
17
- ## 1.1.1
18
-
19
- - README: the XlsxFlow website address, and Pro at a flat $5.
20
- - More npm keywords: `xls` and `ods`.
21
-
22
- ## 1.1.0
23
-
24
- ### Added
25
-
26
- - `SheetWriter`: workbook properties (title, author, company and so on), defined names, hidden and very hidden sheets, and sheet views (zoom, gridlines, headings, right-to-left). `SheetReader.readWorkbook` reads them back.
27
- - `{ formatted: true }` reports each cell's text as Excel shows it, from its number format.
28
- - `SheetEditor.insertRows`, `deleteRows`, `insertColumns` and `deleteColumns`. Everything that points at the moved cells moves with them: formulas on all sheets, defined names, merges, conditional formats, validations, hyperlinks, filters, page breaks, column widths, tables, pictures, notes, sparklines, data tables, chart series and pivot sources.
29
- - `SheetEditor.setCells` edits and restyles cells of existing files; `addSheet` and `deleteSheet` add and remove sheets.
30
- - `parseCsv` streams CSV rows, which `SheetWriter.addSheet` turns into a sheet.
31
- - `SheetReader` reads Excel 97-2003 `.xls` files and OpenDocument `.ods` files: values, dates, formula results, merges, hidden rows, columns and sheets, frozen panes, defined names and document properties. `.ods` also gives formulas, hyperlinks and notes.
32
- - `OdsWriter` writes `.ods` files: values, dates, formulas, merges, column widths, frozen panes and hidden sheets.
33
- - Password-protected `.xlsx` files are rejected with an error that points to `decryptWorkbook` in `@xlsxflow/pro`, instead of a ZIP error.
34
- - Cell notes on write (plain or formatted text) and `getComments()` on read.
35
- - Conditional formats `cellIs`, `expression`, `top10`, `aboveAverage`, text rules, `duplicateValues`/`uniqueValues` and `iconSet`.
36
- - Excel tables, sheet protection, page setup (margins, header and footer, print area and titles), row heights, hidden rows and columns, outline grouping, tab colour, and validation operators and messages.
37
- - `Date` cell values, hyperlinks (URLs and locations in the workbook), `autoFilter`, and an optional shared string table on write.
38
- - Reading returns formulas (`formulas: true`, shared formulas expanded), styles (`styles: true`) and rich text (`richText: true`). Theme and indexed colours are resolved to ARGB.
39
- - PNG, JPEG and GIF images (`images` sheet option), anchored to a cell or stretched over a range. `getImages()` reads them back.
40
- - Low-level parts are exported for add-ons: the ZIP reader and writer, `resolveWorkbookParts`, `readSharedStrings`, and `mapFormulaRefs`/`shiftFormula`.
41
- - Tested: `.xlsm` files keep their macros through `SheetEditor`; the library runs in Cloudflare Workers without `nodejs_compat` (`scripts/workers` checks it).
42
-
43
- ### Security
44
-
45
- Fixed crashes, hangs and memory blowups caused by crafted files:
46
-
47
- - A ZIP directory pointing outside the file crashed Node.
48
- - Unclosed tags and elements made the parser quadratic. The XML tokenizer is now linear on giant tags, text nodes and CDATA.
49
- - Repeated hidden columns, far-right cells, fraction formats with long denominators and large style tables were slow or used unbounded memory.
50
- - One deflated entry could be read many times under different names.
51
-
52
- Also:
53
-
54
- - `SheetEditor.edit` takes `maxUncompressedBytes`.
55
- - Option values typed as enums are escaped, and appended rows encode control characters.
56
- - `sheetToJson` keeps a `__proto__` header as an ordinary key.
57
- - In-memory parts are capped at 1 GiB uncompressed by default.
58
-
59
- ### Fixed
60
-
61
- - Sheets with frozen panes were all marked as selected, so Excel opened them grouped.
62
- - The reader dropped or corrupted cells at stream chunk boundaries.
63
- - Styles pointed at the wrong font, fill or border, and styles used only by `AsyncIterable` rows were missing from `styles.xml`.
64
- - Number formats with quoted text, escapes or colours (`#,##0.00 "USD"`, `[Red]0.0`) were taken for date formats.
65
- - Reading a corrupt file without calling `getMetadata()` caused an unhandled promise rejection, which is fatal in Node.
66
- - `addSheet` accepted sheet names Excel refuses to open. It now throws.
67
- - Files Excel would repair:
68
- - gradient stops were written as `<gradientStop>`;
69
- - `vertical: 'middle'` was written as is instead of `center`;
70
- - conditional formats came after validations.
71
- - Data bar `minValue`/`maxValue` were ignored, every conditional format had priority 1, and colours and ranges were not XML-escaped.
72
- - Strings with `_xHHHH_`, control characters, CR, or leading and trailing spaces now survive a round trip. `NaN` and `Infinity` are written as `#NUM!`.
73
- - Reader:
74
- - Strict OOXML and non-standard part names work;
75
- - empty `<v/>` reads as empty and `-0` as `0`;
76
- - out-of-order cells land in the right column;
77
- - time-only values are no longer a day off, and datetimes keep milliseconds.
78
- - frozen panes saved by Excel as `frozenSplit` (frozen after a split) are reported.
79
- - `sheetToJson` ignored its `headerRowIndex` argument.
80
-
81
- ### Changed
82
-
83
- - The package ships compiled ESM and CommonJS builds with type declarations.
84
- - Browser bundles no longer try to resolve Node's `fs`.
85
- - `[Content_Types].xml` is written last in the ZIP.
86
- - Real backpressure in the ZIP writer and worksheet stream. Producer errors now error the output stream instead of hanging it.
87
- - About 2.5× faster reads, and a table-driven CRC-32 for faster writes.
88
- - Styles, formulas and conditional formats are part of the MIT core. Paid add-ons are in the separate `@xlsxflow/pro` package.
89
- - Benchmarks corrected: the earlier ExcelJS write time came from a cold first run, and write-excel-file was run with an old API.
90
-
91
- ## 1.0.0
92
-
93
- - `SheetWriter` and `SheetEditor` stream the ZIP with data descriptors instead of buffering the file in memory.
94
- - Dates are returned as ISO-8601 strings, detected from each cell's number format.
95
-
96
- ## 0.3.0-beta
97
-
98
- - Workbooks with several sheets.
99
- - Fixed a buffer bug that made parsing quadratic.
100
-
101
- ## 0.2.0-beta
102
-
103
- - Deflate compression with `CompressionStream`: a 1M-cell file went from 30 MB to 2.9 MB.
104
- - Inline and rich text strings are read.
105
-
106
- ## 0.1.0-beta
107
-
108
- - First release: streaming reader, writer, and shared strings.
1
+ # Changelog
2
+
3
+ ## 1.1.3
4
+
5
+ ### Fixed
6
+
7
+ - `SheetEditor.setCells` refuses addresses past column XFD or row 1,048,576, instead of writing a file Excel reports as damaged.
8
+ - `SheetWriter` and `SheetEditor` refuse text longer than Excel's 32,767 characters per cell.
9
+ - Colours are checked: ARGB (`FFFF0000`) and RGB (`FF0000`, `#FF0000`) hex are accepted, anything else (`'red'`) throws instead of making Excel repair the file.
10
+ - Picture anchors past the last column or row throw; a range given the wrong way round (`F20:A2`) is turned round.
11
+ - `SheetReader`: a shared string index past the end of the table reads as an empty cell, not as the index.
12
+
13
+ ### Added
14
+
15
+ - `checkCellText` is exported, for `@xlsxflow/pro`.
16
+
17
+ ## 1.1.2
18
+
19
+ ### Fixed
20
+
21
+ - `SheetWriter` throws an error for a row longer than 16,384 cells or past row 1,048,576, instead of writing a file Excel reports as damaged.
22
+ - `SheetEditor.setCells`: a date in a cell without a date format gets `yyyy-mm-dd` (or `yyyy-mm-dd hh:mm:ss` with a time), as in `SheetWriter`, instead of showing as a serial number.
23
+ - `OdsWriter` stores formula results, so readers that don't recalculate show them.
24
+ - `sheetToJson` keeps every column when headers repeat: the second `Name` becomes `Name_2`.
25
+ - `parseCsv` returns `null` for empty unquoted fields with `{ convert: false }` too.
26
+
27
+ ### Added
28
+
29
+ - `StylePatcher` is exported, for `@xlsxflow/pro`.
30
+
31
+ ## 1.1.1
32
+
33
+ - README: the XlsxFlow website address, and Pro at a flat $5.
34
+ - More npm keywords: `xls` and `ods`.
35
+
36
+ ## 1.1.0
37
+
38
+ ### Added
39
+
40
+ - `SheetWriter`: workbook properties (title, author, company and so on), defined names, hidden and very hidden sheets, and sheet views (zoom, gridlines, headings, right-to-left). `SheetReader.readWorkbook` reads them back.
41
+ - `{ formatted: true }` reports each cell's text as Excel shows it, from its number format.
42
+ - `SheetEditor.insertRows`, `deleteRows`, `insertColumns` and `deleteColumns`. Everything that points at the moved cells moves with them: formulas on all sheets, defined names, merges, conditional formats, validations, hyperlinks, filters, page breaks, column widths, tables, pictures, notes, sparklines, data tables, chart series and pivot sources.
43
+ - `SheetEditor.setCells` edits and restyles cells of existing files; `addSheet` and `deleteSheet` add and remove sheets.
44
+ - `parseCsv` streams CSV rows, which `SheetWriter.addSheet` turns into a sheet.
45
+ - `SheetReader` reads Excel 97-2003 `.xls` files and OpenDocument `.ods` files: values, dates, formula results, merges, hidden rows, columns and sheets, frozen panes, defined names and document properties. `.ods` also gives formulas, hyperlinks and notes.
46
+ - `OdsWriter` writes `.ods` files: values, dates, formulas, merges, column widths, frozen panes and hidden sheets.
47
+ - Password-protected `.xlsx` files are rejected with an error that points to `decryptWorkbook` in `@xlsxflow/pro`, instead of a ZIP error.
48
+ - Cell notes on write (plain or formatted text) and `getComments()` on read.
49
+ - Conditional formats `cellIs`, `expression`, `top10`, `aboveAverage`, text rules, `duplicateValues`/`uniqueValues` and `iconSet`.
50
+ - Excel tables, sheet protection, page setup (margins, header and footer, print area and titles), row heights, hidden rows and columns, outline grouping, tab colour, and validation operators and messages.
51
+ - `Date` cell values, hyperlinks (URLs and locations in the workbook), `autoFilter`, and an optional shared string table on write.
52
+ - Reading returns formulas (`formulas: true`, shared formulas expanded), styles (`styles: true`) and rich text (`richText: true`). Theme and indexed colours are resolved to ARGB.
53
+ - PNG, JPEG and GIF images (`images` sheet option), anchored to a cell or stretched over a range. `getImages()` reads them back.
54
+ - Low-level parts are exported for add-ons: the ZIP reader and writer, `resolveWorkbookParts`, `readSharedStrings`, and `mapFormulaRefs`/`shiftFormula`.
55
+ - Tested: `.xlsm` files keep their macros through `SheetEditor`; the library runs in Cloudflare Workers without `nodejs_compat` (`scripts/workers` checks it).
56
+
57
+ ### Security
58
+
59
+ Fixed crashes, hangs and memory blowups caused by crafted files:
60
+
61
+ - A ZIP directory pointing outside the file crashed Node.
62
+ - Unclosed tags and elements made the parser quadratic. The XML tokenizer is now linear on giant tags, text nodes and CDATA.
63
+ - Repeated hidden columns, far-right cells, fraction formats with long denominators and large style tables were slow or used unbounded memory.
64
+ - One deflated entry could be read many times under different names.
65
+
66
+ Also:
67
+
68
+ - `SheetEditor.edit` takes `maxUncompressedBytes`.
69
+ - Option values typed as enums are escaped, and appended rows encode control characters.
70
+ - `sheetToJson` keeps a `__proto__` header as an ordinary key.
71
+ - In-memory parts are capped at 1 GiB uncompressed by default.
72
+
73
+ ### Fixed
74
+
75
+ - Sheets with frozen panes were all marked as selected, so Excel opened them grouped.
76
+ - The reader dropped or corrupted cells at stream chunk boundaries.
77
+ - Styles pointed at the wrong font, fill or border, and styles used only by `AsyncIterable` rows were missing from `styles.xml`.
78
+ - Number formats with quoted text, escapes or colours (`#,##0.00 "USD"`, `[Red]0.0`) were taken for date formats.
79
+ - Reading a corrupt file without calling `getMetadata()` caused an unhandled promise rejection, which is fatal in Node.
80
+ - `addSheet` accepted sheet names Excel refuses to open. It now throws.
81
+ - Files Excel would repair:
82
+ - gradient stops were written as `<gradientStop>`;
83
+ - `vertical: 'middle'` was written as is instead of `center`;
84
+ - conditional formats came after validations.
85
+ - Data bar `minValue`/`maxValue` were ignored, every conditional format had priority 1, and colours and ranges were not XML-escaped.
86
+ - Strings with `_xHHHH_`, control characters, CR, or leading and trailing spaces now survive a round trip. `NaN` and `Infinity` are written as `#NUM!`.
87
+ - Reader:
88
+ - Strict OOXML and non-standard part names work;
89
+ - empty `<v/>` reads as empty and `-0` as `0`;
90
+ - out-of-order cells land in the right column;
91
+ - time-only values are no longer a day off, and datetimes keep milliseconds.
92
+ - frozen panes saved by Excel as `frozenSplit` (frozen after a split) are reported.
93
+ - `sheetToJson` ignored its `headerRowIndex` argument.
94
+
95
+ ### Changed
96
+
97
+ - The package ships compiled ESM and CommonJS builds with type declarations.
98
+ - Browser bundles no longer try to resolve Node's `fs`.
99
+ - `[Content_Types].xml` is written last in the ZIP.
100
+ - Real backpressure in the ZIP writer and worksheet stream. Producer errors now error the output stream instead of hanging it.
101
+ - About 2.5× faster reads, and a table-driven CRC-32 for faster writes.
102
+ - Styles, formulas and conditional formats are part of the MIT core. Paid add-ons are in the separate `@xlsxflow/pro` package.
103
+ - Benchmarks corrected: the earlier ExcelJS write time came from a cold first run, and write-excel-file was run with an old API.
104
+
105
+ ## 1.0.0
106
+
107
+ - `SheetWriter` and `SheetEditor` stream the ZIP with data descriptors instead of buffering the file in memory.
108
+ - Dates are returned as ISO-8601 strings, detected from each cell's number format.
109
+
110
+ ## 0.3.0-beta
111
+
112
+ - Workbooks with several sheets.
113
+ - Fixed a buffer bug that made parsing quadratic.
114
+
115
+ ## 0.2.0-beta
116
+
117
+ - Deflate compression with `CompressionStream`: a 1M-cell file went from 30 MB to 2.9 MB.
118
+ - Inline and rich text strings are read.
119
+
120
+ ## 0.1.0-beta
121
+
122
+ - First release: streaming reader, writer, and shared strings.
package/README.md CHANGED
@@ -224,6 +224,8 @@ writer.addSheet('Sales', [
224
224
  });
225
225
  ```
226
226
 
227
+ Colours are ARGB hex (`FFC00000`); RGB hex (`C00000` or `#C00000`) is opaque. A cell holds at most 32,767 characters, as in Excel.
228
+
227
229
  Other sheet options:
228
230
 
229
231
  ```typescript
package/dist/index.cjs CHANGED
@@ -464,6 +464,21 @@ function decodeXString(s) {
464
464
  // Excel's sheet size (A..XFD, 1..1048576)
465
465
  const MAX_COLUMNS$1 = 16384;
466
466
  const MAX_ROWS$1 = 1048576;
467
+ // Excel's limit on the characters in one cell
468
+ const MAX_CELL_TEXT = 32767;
469
+ function checkCellText(s, ref) {
470
+ if (s.length > MAX_CELL_TEXT)
471
+ throw new Error(`Cell ${ref} has ${s.length} characters, more than Excel's ${MAX_CELL_TEXT}.`);
472
+ return s;
473
+ }
474
+ // Colours are ARGB hex ("FFFF0000"); "FF0000" and "#FF0000" get an opaque alpha. Anything else
475
+ // makes Excel repair the file.
476
+ function argb(c) {
477
+ const m = /^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.exec(String(c));
478
+ if (!m)
479
+ throw new Error(`Invalid colour "${c}": use ARGB hex such as "FFFF0000".`);
480
+ return (m[1].length === 6 ? 'FF' + m[1] : m[1]).toUpperCase();
481
+ }
467
482
  // 0-based column of letters like "BC" (or a reference like "BC12"); -1 when there are none
468
483
  function colIndex(ref) {
469
484
  const match = ref.match(/[A-Za-z]+/);
@@ -1461,7 +1476,8 @@ function parseWorksheet(xmlTokenStream, sharedStrings, styles, is1904 = false, o
1461
1476
  }
1462
1477
  else if (currentCellType === 's') {
1463
1478
  const index = parseInt(currentCellValue, 10);
1464
- resolvedValue = sharedStrings.get(index) ?? currentCellValue;
1479
+ // An index past the table points nowhere: the cell is empty
1480
+ resolvedValue = sharedStrings.get(index) ?? null;
1465
1481
  }
1466
1482
  else if (currentCellType === 'b') {
1467
1483
  resolvedValue = currentCellValue === '1' || currentCellValue === 'true';
@@ -1635,9 +1651,10 @@ async function readSheetImages(readText, readBytes, sheetPath) {
1635
1651
  const escapeXml$4 = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1636
1652
  const cellPos = (ref) => {
1637
1653
  const m = /^\$?([A-Za-z]{1,3})\$?(\d+)$/.exec(ref.trim());
1638
- if (!m)
1654
+ const col = m ? colIndex(m[1]) : -1, row = m ? parseInt(m[2], 10) - 1 : -1;
1655
+ if (col < 0 || col >= MAX_COLUMNS$1 || row < 0 || row >= MAX_ROWS$1)
1639
1656
  throw new Error(`Invalid anchor cell "${ref}".`);
1640
- return { col: colIndex(m[1]), row: parseInt(m[2], 10) - 1 };
1657
+ return { col, row };
1641
1658
  };
1642
1659
  const marker = (tag, { col, row }) => `<xdr:${tag}><xdr:col>${col}</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>${row}</xdr:row><xdr:rowOff>0</xdr:rowOff></xdr:${tag}>`;
1643
1660
  const DRAWING_NS = 'xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" ' +
@@ -1649,9 +1666,11 @@ const DRAWING_NS = 'xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/
1649
1666
  function anchorXml(place, natural, body, attrs = '') {
1650
1667
  if ('range' in place) {
1651
1668
  const [from, to = from] = place.range.split(':');
1652
- const end = cellPos(to);
1653
- // The object fills the range: it ends at the far edge of the last cell
1654
- return `<xdr:twoCellAnchor editAs="oneCell"${attrs}>${marker('from', cellPos(from))}${marker('to', { col: end.col + 1, row: end.row + 1 })}${body}<xdr:clientData/></xdr:twoCellAnchor>`;
1669
+ const a = cellPos(from), b = cellPos(to);
1670
+ // The object fills the range, given either way round: it ends at the far edge of the last cell
1671
+ const start = { col: Math.min(a.col, b.col), row: Math.min(a.row, b.row) };
1672
+ const end = { col: Math.max(a.col, b.col) + 1, row: Math.max(a.row, b.row) + 1 };
1673
+ return `<xdr:twoCellAnchor editAs="oneCell"${attrs}>${marker('from', start)}${marker('to', end)}${body}<xdr:clientData/></xdr:twoCellAnchor>`;
1655
1674
  }
1656
1675
  const { width: w, height: h } = natural;
1657
1676
  const width = place.width ?? (place.height ? w * place.height / h : w);
@@ -2636,22 +2655,22 @@ function fontXml(font, nameTag = 'name') {
2636
2655
  if (font.size)
2637
2656
  xml += `<sz val="${escapeXml$3(font.size)}"/>`;
2638
2657
  if (font.color)
2639
- xml += `<color rgb="${escapeXml$3(font.color)}"/>`;
2658
+ xml += `<color rgb="${argb(font.color)}"/>`;
2640
2659
  if (font.name)
2641
2660
  xml += `<${nameTag} val="${escapeXml$3(font.name)}"/>`;
2642
2661
  return xml;
2643
2662
  }
2644
- const borderSideXml = (tag, s) => s ? `<${tag} style="${escapeXml$3(s.style)}">${s.color ? `<color rgb="${escapeXml$3(s.color)}"/>` : ''}</${tag}>` : `<${tag}/>`;
2663
+ const borderSideXml = (tag, s) => s ? `<${tag} style="${escapeXml$3(s.style)}">${s.color ? `<color rgb="${argb(s.color)}"/>` : ''}</${tag}>` : `<${tag}/>`;
2645
2664
  function borderXml(b) {
2646
2665
  const side = borderSideXml;
2647
2666
  return `<border>${side('left', b.left)}${side('right', b.right)}${side('top', b.top)}${side('bottom', b.bottom)}<diagonal/></border>`;
2648
2667
  }
2649
2668
  function fillXml(fill) {
2650
2669
  if (fill.type === 'gradient') {
2651
- const stops = fill.stops.map(s => `<stop position="${s.position}"><color rgb="${escapeXml$3(s.color)}"/></stop>`).join('');
2670
+ const stops = fill.stops.map(s => `<stop position="${s.position}"><color rgb="${argb(s.color)}"/></stop>`).join('');
2652
2671
  return `<fill><gradientFill degree="${escapeXml$3(fill.degree ?? 0)}">${stops}</gradientFill></fill>`;
2653
2672
  }
2654
- return `<fill><patternFill patternType="solid"><fgColor rgb="${escapeXml$3(fill.fgColor)}"/></patternFill></fill>`;
2673
+ return `<fill><patternFill patternType="solid"><fgColor rgb="${argb(fill.fgColor)}"/></patternFill></fill>`;
2655
2674
  }
2656
2675
  // Attributes of an <alignment> element
2657
2676
  function alignmentAttrs(alignment) {
@@ -2693,7 +2712,7 @@ class StyleEngine {
2693
2712
  xml += `<numFmt numFmtId="${this.registerNumFmt(style.numFmt)}" formatCode="${escapeXml$3(style.numFmt)}"/>`;
2694
2713
  if (style.fill?.type === 'solid') {
2695
2714
  // A differential solid fill takes its colour from bgColor
2696
- const c = escapeXml$3(style.fill.fgColor);
2715
+ const c = argb(style.fill.fgColor);
2697
2716
  xml += `<fill><patternFill patternType="solid"><fgColor rgb="${c}"/><bgColor rgb="${c}"/></patternFill></fill>`;
2698
2717
  }
2699
2718
  if (style.border)
@@ -2821,7 +2840,7 @@ ${this.dxfs.size ? `<dxfs count="${this.dxfs.size}">${[...this.dxfs.keys()].map(
2821
2840
  function escapeXml$2(val) {
2822
2841
  return String(val).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2823
2842
  }
2824
- const color = (rgb) => `<color rgb="${escapeXml$2(rgb)}"/>`;
2843
+ const color = (rgb) => `<color rgb="${argb(rgb)}"/>`;
2825
2844
  const bound = (type, value) => value === undefined ? `<cfvo type="${type}"/>` : `<cfvo type="num" val="${escapeXml$2(value)}"/>`;
2826
2845
  const formula = (f) => `<formula>${escapeXml$2(String(f).replace(/^=/, ''))}</formula>`;
2827
2846
  // Excel string literal inside a formula
@@ -3399,13 +3418,13 @@ const W3CDTF = (d) => d.toISOString().replace(/\.\d{3}Z$/, 'Z');
3399
3418
  const propXml = (tag, v) => v === undefined ? '' : `<${tag}>${escapeXml$1(v)}</${tag}>`;
3400
3419
  function corePropsXml(p) {
3401
3420
  const created = p.created ? `<dcterms:created xsi:type="dcterms:W3CDTF">${W3CDTF(p.created)}</dcterms:created>` : '';
3402
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3421
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3403
3422
  <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">` +
3404
3423
  propXml('dc:title', p.title) + propXml('dc:subject', p.subject) + propXml('dc:creator', p.creator) + propXml('cp:keywords', p.keywords) +
3405
3424
  propXml('dc:description', p.description) + propXml('cp:category', p.category) + created + '</cp:coreProperties>';
3406
3425
  }
3407
3426
  function appPropsXml(p) {
3408
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3427
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3409
3428
  <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>XlsxFlow</Application>${propXml('Manager', p.manager)}${propXml('Company', p.company)}</Properties>`;
3410
3429
  }
3411
3430
  // A name Excel accepts: letters, digits, _ . and \, not a cell reference (A1, R1C1) and not reserved
@@ -3573,7 +3592,7 @@ class SheetWriter {
3573
3592
  });
3574
3593
  await this.streamString(zip, `xl/drawings/drawing${n}.xml`, drawingXml(images, images.map(img => this.media.get(img.data).info), rIds));
3575
3594
  const rels = targets.map((t, k) => `<Relationship Id="rId${k + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="${t}"/>`).join('');
3576
- await this.streamString(zip, `xl/drawings/_rels/drawing${n}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3595
+ await this.streamString(zip, `xl/drawings/_rels/drawing${n}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3577
3596
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`);
3578
3597
  }
3579
3598
  applyAutoFit(rows, options) {
@@ -3650,7 +3669,7 @@ class SheetWriter {
3650
3669
  const page = options.pageSetup;
3651
3670
  const fitToPage = page && (page.fitToWidth !== undefined || page.fitToHeight !== undefined);
3652
3671
  const sheetPr = options.tabColor || fitToPage
3653
- ? `<sheetPr>${options.tabColor ? `<tabColor rgb="${escapeXml$1(options.tabColor)}"/>` : ''}${fitToPage ? '<pageSetUpPr fitToPage="1"/>' : ''}</sheetPr>`
3672
+ ? `<sheetPr>${options.tabColor ? `<tabColor rgb="${argb(options.tabColor)}"/>` : ''}${fitToPage ? '<pageSetUpPr fitToPage="1"/>' : ''}</sheetPr>`
3654
3673
  : '';
3655
3674
  const sheetFormatPr = outlineRow || outlineCol
3656
3675
  ? `<sheetFormatPr defaultRowHeight="15"${outlineRow ? ` outlineLevelRow="${outlineRow}"` : ''}${outlineCol ? ` outlineLevelCol="${outlineCol}"` : ''}/>`
@@ -3754,6 +3773,7 @@ class SheetWriter {
3754
3773
  return `<c r="${colRef}"${sAttr}><v>${dateToSerial(val)}</v></c>`;
3755
3774
  }
3756
3775
  if (typeof val === 'string') {
3776
+ checkCellText(val, colRef);
3757
3777
  if (this.writerOptions.sharedStrings) {
3758
3778
  let index = this.sharedStrings.get(val);
3759
3779
  if (index === undefined)
@@ -3841,7 +3861,7 @@ class SheetWriter {
3841
3861
  let items = '';
3842
3862
  for (const s of this.sharedStrings.keys())
3843
3863
  items += `<si>${stringXml(s)}</si>`;
3844
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3864
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3845
3865
  <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${this.sharedStringRefs}" uniqueCount="${this.sharedStrings.size}">${items}</sst>`;
3846
3866
  }
3847
3867
  // '' when the sheet has no relationships
@@ -3856,7 +3876,7 @@ class SheetWriter {
3856
3876
  tableIds.forEach((id, k) => { rels += `<Relationship Id="rIdTable${k + 1}" Type="${REL}/table" Target="../tables/table${id}.xml"/>`; });
3857
3877
  if (!rels)
3858
3878
  return '';
3859
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3879
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3860
3880
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`;
3861
3881
  }
3862
3882
  buildWorkbookXml() {
@@ -3881,14 +3901,14 @@ class SheetWriter {
3881
3901
  }
3882
3902
  return names;
3883
3903
  }).join('');
3884
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3885
- <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
3886
- xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
3887
- ${active > 0 ? `<bookViews><workbookView firstSheet="${active}" activeTab="${active}"/></bookViews>` : ''}
3888
- <sheets>
3889
- ${sheetsXml}
3890
- </sheets>
3891
- ${filterNames || userNames ? `<definedNames>${filterNames}${userNames}</definedNames>` : ''}
3904
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3905
+ <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
3906
+ xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
3907
+ ${active > 0 ? `<bookViews><workbookView firstSheet="${active}" activeTab="${active}"/></bookViews>` : ''}
3908
+ <sheets>
3909
+ ${sheetsXml}
3910
+ </sheets>
3911
+ ${filterNames || userNames ? `<definedNames>${filterNames}${userNames}</definedNames>` : ''}
3892
3912
  </workbook>`;
3893
3913
  }
3894
3914
  // The first visible sheet, which Excel opens on
@@ -3899,19 +3919,19 @@ class SheetWriter {
3899
3919
  const sheetRels = this.sheets.map((_, i) => `<Relationship Id="rId${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`).join('\n ');
3900
3920
  const ssId = this.sheets.length + 1;
3901
3921
  const stylesId = this.sheets.length + 2;
3902
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3903
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3904
- ${sheetRels}
3905
- <Relationship Id="rId${ssId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
3906
- <Relationship Id="rId${stylesId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
3922
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3923
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3924
+ ${sheetRels}
3925
+ <Relationship Id="rId${ssId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
3926
+ <Relationship Id="rId${stylesId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
3907
3927
  </Relationships>`;
3908
3928
  }
3909
3929
  buildRootRels() {
3910
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3911
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3912
- <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>${this.writerOptions.properties ? `
3913
- <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
3914
- <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>` : ''}
3930
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3931
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3932
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>${this.writerOptions.properties ? `
3933
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
3934
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>` : ''}
3915
3935
  </Relationships>`;
3916
3936
  }
3917
3937
  buildContentTypes(drawings, commentSheets, tables) {
@@ -3922,17 +3942,17 @@ class SheetWriter {
3922
3942
  commentSheets.map(n => `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`).join('') +
3923
3943
  Array.from({ length: tables }, (_, i) => `<Override PartName="/xl/tables/table${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"/>`).join('') +
3924
3944
  (commentSheets.length ? '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>' : '');
3925
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3926
- <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
3927
- <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
3928
- <Default Extension="xml" ContentType="application/xml"/>
3929
- ${imageTypes}${drawingOverrides}
3930
- <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
3931
- ${sheetOverrides}
3932
- <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
3933
- <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${this.writerOptions.properties ? `
3934
- <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
3935
- <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>` : ''}
3945
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3946
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
3947
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
3948
+ <Default Extension="xml" ContentType="application/xml"/>
3949
+ ${imageTypes}${drawingOverrides}
3950
+ <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
3951
+ ${sheetOverrides}
3952
+ <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
3953
+ <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${this.writerOptions.properties ? `
3954
+ <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
3955
+ <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>` : ''}
3936
3956
  </Types>`;
3937
3957
  }
3938
3958
  }
@@ -3964,7 +3984,7 @@ function pageSetupXml(page) {
3964
3984
  function commentsXml(comments) {
3965
3985
  const authors = [...new Set(comments.map(c => c.comment.author ?? ''))];
3966
3986
  const list = comments.map(c => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.comment.author ?? '')}"><text>${runsXml(c.comment.text)}</text></comment>`).join('');
3967
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3987
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3968
3988
  <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authors.map(a => `<author>${escapeXml$1(a)}</author>`).join('')}</authors><commentList>${list}</commentList></comments>`;
3969
3989
  }
3970
3990
  // The legacy VML shapes Excel needs to show the note boxes (hidden until the cell is hovered)
@@ -3984,7 +4004,7 @@ function tableXml(table, id, columns) {
3984
4004
  const ref = table.ref.replace(/\$/g, '');
3985
4005
  const filter = table.autoFilter === false ? '' : `<autoFilter ref="${ref}"/>`;
3986
4006
  const cols = columns.map((c, k) => `<tableColumn id="${k + 1}" name="${escapeXml$1(c)}"/>`).join('');
3987
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4007
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3988
4008
  <table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="${id}" name="${escapeXml$1(table.name)}" displayName="${escapeXml$1(table.name)}" ref="${ref}" totalsRowShown="0">` +
3989
4009
  `${filter}<tableColumns count="${columns.length}">${cols}</tableColumns>` +
3990
4010
  `<tableStyleInfo name="${escapeXml$1(table.style ?? 'TableStyleMedium2')}" showFirstColumn="0" showLastColumn="0" showRowStripes="${table.showRowStripes === false ? 0 : 1}" showColumnStripes="0"/></table>`;
@@ -4724,7 +4744,7 @@ function editedCell(p, ref, attrs, edit) {
4724
4744
  }
4725
4745
  if (typeof edit === 'object')
4726
4746
  return `<${p}c r="${ref}"${attrs}><${p}f>${escapeXml$5(edit.formula.replace(/^=/, ''))}</${p}f></${p}c>`;
4727
- const text = escapeXml$5(encodeXString(edit));
4747
+ const text = escapeXml$5(encodeXString(checkCellText(edit, ref)));
4728
4748
  const t = /^\s|\s$/.test(edit) ? `<${p}t xml:space="preserve">${text}</${p}t>` : `<${p}t>${text}</${p}t>`;
4729
4749
  return `<${p}c r="${ref}"${attrs} t="inlineStr"><${p}is>${t}</${p}is></${p}c>`;
4730
4750
  }
@@ -4775,9 +4795,9 @@ class SheetEditor {
4775
4795
  this.cellEdits.set(sheetName, rows = new Map());
4776
4796
  for (const [ref, edit] of Object.entries(cells)) {
4777
4797
  const m = /^\$?([A-Za-z]{1,3})\$?(\d+)$/.exec(ref);
4778
- if (!m || parseInt(m[2], 10) < 1)
4798
+ const r = m ? parseInt(m[2], 10) : 0;
4799
+ if (!m || r < 1 || r > MAX_ROWS$1 || colIndex(m[1]) >= MAX_COLUMNS$1)
4779
4800
  throw new Error(`Invalid cell reference "${ref}".`);
4780
- const r = parseInt(m[2], 10);
4781
4801
  if (!rows.has(r))
4782
4802
  rows.set(r, new Map());
4783
4803
  rows.get(r).set(colIndex(m[1]), edit);
@@ -5196,7 +5216,7 @@ class SheetEditor {
5196
5216
  if (typeof val === 'number')
5197
5217
  return `<c r="${colRef}"><v>${val}</v></c>`;
5198
5218
  if (typeof val === 'string') {
5199
- return `<c r="${colRef}" t="inlineStr"><is><t xml:space="preserve">${escapeXml$5(encodeXString(val))}</t></is></c>`;
5219
+ return `<c r="${colRef}" t="inlineStr"><is><t xml:space="preserve">${escapeXml$5(encodeXString(checkCellText(val, colRef)))}</t></is></c>`;
5200
5220
  }
5201
5221
  return `<c r="${colRef}"/>`;
5202
5222
  }).join('');
@@ -6156,6 +6176,7 @@ exports.XlsxFlow = XlsxFlow;
6156
6176
  exports.ZipRandomAccessParser = ZipRandomAccessParser;
6157
6177
  exports.ZipStreamWriter = ZipStreamWriter;
6158
6178
  exports.anchorXml = anchorXml;
6179
+ exports.checkCellText = checkCellText;
6159
6180
  exports.colIndex = colIndex;
6160
6181
  exports.colLetter = colLetter;
6161
6182
  exports.crc32 = crc32;