style-xlsx-m 0.1.34 → 0.1.35

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  Read and write `.xlsx` files with selected cell styles in Node.js or bundled browser code, and set page setup for printing.
4
4
 
5
+ ![style-xlsx-m: read a styled Excel template, edit its data, and save it with supported formatting kept](docs/images/style-xlsx-m-cover.png)
6
+
5
7
  Based on [SheetJS Community Edition](https://github.com/SheetJS/sheetjs) (`xlsx` 0.18.5) and [xlsx-js-style](https://github.com/gitbrent/xlsx-js-style).
6
8
 
7
9
  > **Security note:** this package is built on SheetJS 0.18.5, which has known vulnerabilities fixed upstream in later versions (prototype pollution, CVE-2023-30533, and ReDoS, CVE-2024-22363). **Do not use it to parse untrusted files.** `npm audit` will not flag this, because the SheetJS code is bundled into the package rather than installed as a dependency.
@@ -52,6 +54,10 @@ For everything else (`utils`, `read` options, other formats) see the [SheetJS do
52
54
 
53
55
  Pass `{ cellStyles: true }` to `readFile` (or `read`). Styled cells expose an `s` object for the writer. The independent openpyxl fixture confirms RGB solid fills, a theme-4 fill with tint, RGB/theme borders, font name/size/colour, bold/italic/single underline, and alignment. Number/date formats, merges, column widths, row heights, and A4 landscape page setup with scale 91 also survive that fixture. This is a partial round trip; see the limitations below.
54
56
 
57
+ ![Synthetic sales report before and after editing: Product A units change from 100 to 120, revenue from 1,250.00 to 1,500.00, and total revenue from 6,100.00 to 6,350.00, with the same cell formatting](docs/images/template-before-after.png)
58
+
59
+ The images are real LibreOffice renders of `.xlsx` files created with a locally packed build of this package. Blue outlines mark the three changed values. An independent openpyxl check confirmed that fonts, fills, borders and number formats stayed unchanged in this example.
60
+
55
61
  ```js
56
62
  const XLSX = require('style-xlsx-m');
57
63
 
@@ -74,7 +80,24 @@ Note: `styles[r]` lists the cells that `sheet_to_json` visits in that row, in or
74
80
 
75
81
  Reading styles works for `.xlsx` only, not `.xls`.
76
82
 
77
- The `main` and `module` entries use the same style-reading build. In bundled browser code, use `import XLSX from 'style-xlsx-m'` and `XLSX.read(bytes, { type: 'array', cellStyles: true })` with a `Uint8Array` or `ArrayBuffer`. `readFile` requires the Node.js filesystem. Importing, editing and saving a styled template is tested with webpack for browser and Node.js targets.
83
+ The `main` and `module` entries use the same style-reading build. In bundled browser code, use `import XLSX from 'style-xlsx-m'` and `XLSX.read(bytes, { type: 'array', cellStyles: true })` with a `Uint8Array` or `ArrayBuffer`. `readFile` requires the Node.js filesystem. Importing, editing and saving a styled template is tested with webpack for browser and Node.js targets. The corrected local build is also tested in React with Vite in development and production and real Chrome downloads; these corrections have not yet been published to npm.
84
+
85
+ ```js
86
+ import XLSX from 'style-xlsx-m';
87
+
88
+ async function editTemplateInBrowser() {
89
+ const response = await fetch('/Book1.xlsx');
90
+ if (!response.ok) throw new Error(`Template request failed: ${response.status}`);
91
+ const bytes = await response.arrayBuffer();
92
+ const wb = XLSX.read(bytes, { type: 'array', cellStyles: true, cellNF: true });
93
+ const ws = wb.Sheets[wb.SheetNames[0]];
94
+ ws.B3.v = 'Edited in React';
95
+ delete ws.B3.w; // discard the cached display text
96
+ XLSX.writeFile(wb, 'Book1-edited.xlsx');
97
+ }
98
+ ```
99
+
100
+ Call this function from a React button handler. Edit existing cell objects to keep their `s` style; recreating the sheet from values discards those styles. The parser and styled writer are shared with Node.js. Styled blank cells remain blank, and font `family`/`scheme` and theme-index-0 solid fills survive the tested local round trips.
78
101
 
79
102
  ## Cell styles reference
80
103
 
@@ -106,6 +129,8 @@ const style = {
106
129
  font: {
107
130
  name: 'Calibri',
108
131
  sz: 11, // points
132
+ family: 2,
133
+ scheme: 'minor',
109
134
  color: COLOR,
110
135
  bold: false,
111
136
  italic: false,
@@ -141,14 +166,15 @@ When a file is **read**, `ws['!pageSetup']` is the raw `<pageSetup .../>` XML ta
141
166
 
142
167
  - Based on SheetJS 0.18.5 (see the security note above). Do not parse untrusted files.
143
168
  - Cell styles are read from `.xlsx` only, not `.xls`.
144
- - Reading is a partial round trip. A theme-4 solid fill with tint is preserved, but a theme-index-0 fill loses its colour. Its `s.fill.fgColor` contains `{ theme: 0 }`; the bundled upstream writer drops that colour.
169
+ - Reading is a partial round trip. RGB and theme-4 solid fills with tint and a theme-index-0 fill are preserved in the tested local fixtures. These fixes are not yet in the published npm version 0.1.34.
170
+ - Excel Table objects and their table styles (for example, Table1 with striped rows in Book1.xlsx) are not preserved. This limitation applies in both Node.js and browsers; table styles are not cell `s` styles.
145
171
  - An empty sheet returns `[]` even when `getCellStyles: true` is requested.
146
172
  - Indexed border colours are exposed and saved as RGB. The tested indexed red keeps its RGB colour, but loses the indexed palette reference. Theme border colours keep their theme and tint. Custom theme palettes have not been tested.
147
173
  - Single underline is confirmed. Other underline variants, gradient fills, conditional formatting, and diagonal border reading are not verified. Do not assume they are preserved.
148
174
  - Number formats are preserved in the tested numeric and date cells, but are not part of `s`; use `cellNF: true` to expose them in `cell.z`. Merges, column widths and row heights are worksheet metadata (`!merges`, `!cols`, `!rows`), not cell styles.
149
175
  - `!pageSetup` is returned as a raw XML string on read. The original A4/landscape/scale tag is preserved on save. To change it, assign the object shown above with string values. Numeric object values and other print settings are not verified.
150
176
  - The TypeScript definitions are inherited from SheetJS with a `getCellStyles` option added. `s` and `!pageSetup` are not fully typed, and the `sheet_to_json` return types do not describe `{ out, styles }`.
151
- - The `main` and `module` entries share the tested style reader. The separate CDN entries (`unpkg` and `jsdelivr`) remain upstream SheetJS builds and do not provide the same style-reading guarantees. Full Vite and Next.js applications have not been tested.
177
+ - The `main` and `module` entries share the tested style reader. The separate CDN entries (`unpkg` and `jsdelivr`) remain upstream SheetJS builds and do not provide the same style-reading guarantees. The corrected local build is tested in React/Vite; Next.js applications have not been tested.
152
178
  - Only these synchronous write functions are delegated to xlsx-js-style: `write`, `writeFile`, `writeFileSync`, `writeXLSX`, `writeFileXLSX`. `writeFileAsync` uses the inherited SheetJS writer and is outside these style guarantees.
153
179
 
154
180
  ## Credits & License
@@ -161,4 +187,4 @@ Changes in this fork (by Shahrom Shukurov):
161
187
  - Page setup for printing (paper size, orientation, scale)
162
188
 
163
189
  This project is not affiliated with or endorsed by SheetJS LLC.
164
- Licensed under the Apache License 2.0. See [LICENSE](https://github.com/MOR8T/style-xlsx-m/blob/main/LICENSE) and [NOTICE](https://github.com/MOR8T/style-xlsx-m/blob/main/NOTICE).
190
+ Licensed under the Apache License 2.0. See [LICENSE](https://github.com/MOR8T/style-xlsx-m/blob/main/LICENSE) and [NOTICE](https://github.com/MOR8T/style-xlsx-m/blob/main/NOTICE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "style-xlsx-m",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "author": "Shahrom Shukurov",
5
5
  "description": "Fork of SheetJS (xlsx 0.18.5) and xlsx-js-style: read .xlsx files with cell styles, write styled cells, and set page setup for printing.",
6
6
  "keywords": [
@@ -14427,8 +14427,8 @@ function make_xlsx_lib(a) {
14427
14427
  (r = (function (e, t, r, a) {
14428
14428
  if (
14429
14429
  (e.c && r["!comments"].push([t, e.c]),
14430
- (void 0 === e.v && "string" != typeof e.f) ||
14431
- ("z" === e.t && !e.f))
14430
+ ((void 0 === e.v && "string" != typeof e.f) ||
14431
+ ("z" === e.t && !e.f)) && !e.s)
14432
14432
  )
14433
14433
  return "";
14434
14434
  var n = "",
@@ -14458,6 +14458,8 @@ function make_xlsx_lib(a) {
14458
14458
  var o = $t("v", kt(n)),
14459
14459
  c = { r: t },
14460
14460
  l = el(a.cellXfs, e, a);
14461
+ if (("z" === e.t && !e.f) || (null == e.v && "string" != typeof e.f))
14462
+ o = "";
14461
14463
  switch ((0 !== l && (c.s = l), e.t)) {
14462
14464
  case "n":
14463
14465
  break;
@@ -26277,7 +26279,15 @@ function make_xlsx_lib(a) {
26277
26279
  });
26278
26280
  return (
26279
26281
  (a.prototype.escapeAttributeValue = function (e) {
26280
- return '"' + e.replace(/\"/g, "&quot;") + '"';
26282
+ return (
26283
+ '"' +
26284
+ e
26285
+ .replace(/&(?!(?:amp|lt|gt|quot|apos|#[0-9]+|#x[0-9a-fA-F]+);)/g, "&amp;")
26286
+ .replace(/</g, "&lt;")
26287
+ .replace(/>/g, "&gt;")
26288
+ .replace(/\"/g, "&quot;") +
26289
+ '"'
26290
+ );
26281
26291
  }),
26282
26292
  (a.prototype.toXml = function (e) {
26283
26293
  // in this function make style tag
@@ -26457,12 +26467,15 @@ function make_xlsx_lib(a) {
26457
26467
  },
26458
26468
  _addFont: function (e) {
26459
26469
  if (!e) return 0;
26470
+ var fontColor;
26460
26471
  var t = vu("font")
26461
26472
  .append(vu("sz").attr("val", e.sz || this.defaultStyle.font.sz))
26462
26473
  .append(
26463
26474
  vu("name").attr("val", e.name || this.defaultStyle.font.name)
26464
26475
  );
26465
26476
  e.bold && t.append(vu("b")),
26477
+ e.family != null && t.append(vu("family").attr("val", e.family)),
26478
+ e.scheme != null && t.append(vu("scheme").attr("val", e.scheme)),
26466
26479
  e.underline && t.append(vu("u")),
26467
26480
  e.italic && t.append(vu("i")),
26468
26481
  e.strike && t.append(vu("strike")),
@@ -26470,12 +26483,12 @@ function make_xlsx_lib(a) {
26470
26483
  e.shadow && t.append(vu("shadow")),
26471
26484
  e.vertAlign && t.append(vu("vertAlign").attr("val", e.vertAlign)),
26472
26485
  e.color &&
26473
- (e.color.theme
26474
- ? (t.append(vu("color").attr("theme", e.color.theme)),
26475
- e.color.tint &&
26476
- t.append(vu("tint").attr("theme", e.color.tint)))
26486
+ (e.color.theme != null
26487
+ ? (fontColor = vu("color").attr("theme", e.color.theme),
26488
+ e.color.tint != null && fontColor.attr("tint", e.color.tint),
26489
+ t.append(fontColor))
26477
26490
  : e.color.rgb &&
26478
- t.append(vu("color").attr("rgb", e.color.rgb))),
26491
+ t.append(vu("color").attr("rgb", e.color.rgb.length === 6 ? "FF" + e.color.rgb : e.color.rgb))),
26479
26492
  this.$fonts.append(t);
26480
26493
  t = this.$fonts.children().length;
26481
26494
  return this.$fonts.attr("count", t), t - 1;
@@ -26499,7 +26512,7 @@ function make_xlsx_lib(a) {
26499
26512
  return this.$numFmts.attr("count", e), r;
26500
26513
  },
26501
26514
  _addFill: function (e) {
26502
- if (!e) return 0;
26515
+ if (!e || (!e.patternType && !e.fgColor && !e.bgColor)) return 0;
26503
26516
  var t,
26504
26517
  r = vu("patternFill").attr("patternType", e.patternType || "solid");
26505
26518
  e.fgColor &&
@@ -26509,7 +26522,7 @@ function make_xlsx_lib(a) {
26509
26522
  (e.fgColor.rgb = "FF" + e.fgColor.rgb),
26510
26523
  t.attr("rgb", e.fgColor.rgb),
26511
26524
  r.append(t))
26512
- : e.fgColor.theme &&
26525
+ : e.fgColor.theme != null &&
26513
26526
  (t.attr("theme", e.fgColor.theme),
26514
26527
  e.fgColor.tint && t.attr("tint", e.fgColor.tint),
26515
26528
  r.append(t)),
@@ -26530,7 +26543,7 @@ function make_xlsx_lib(a) {
26530
26543
  t.color.auto
26531
26544
  ? e.attr("auto", t.color.auto)
26532
26545
  : t.color.rgb
26533
- ? e.attr("rgb", t.color.rgb)
26546
+ ? e.attr("rgb", t.color.rgb.length === 6 ? "FF" + t.color.rgb : t.color.rgb)
26534
26547
  : (t.color.theme || t.color.tint) &&
26535
26548
  (e.attr("theme", t.color.theme || "1"),
26536
26549
  e.attr("tint", t.color.tint || "0")),
@@ -14427,8 +14427,8 @@ function make_xlsx_lib(a) {
14427
14427
  (r = (function (e, t, r, a) {
14428
14428
  if (
14429
14429
  (e.c && r["!comments"].push([t, e.c]),
14430
- (void 0 === e.v && "string" != typeof e.f) ||
14431
- ("z" === e.t && !e.f))
14430
+ ((void 0 === e.v && "string" != typeof e.f) ||
14431
+ ("z" === e.t && !e.f)) && !e.s)
14432
14432
  )
14433
14433
  return "";
14434
14434
  var n = "",
@@ -14458,6 +14458,8 @@ function make_xlsx_lib(a) {
14458
14458
  var o = $t("v", kt(n)),
14459
14459
  c = { r: t },
14460
14460
  l = el(a.cellXfs, e, a);
14461
+ if (("z" === e.t && !e.f) || (null == e.v && "string" != typeof e.f))
14462
+ o = "";
14461
14463
  switch ((0 !== l && (c.s = l), e.t)) {
14462
14464
  case "n":
14463
14465
  break;
@@ -26277,7 +26279,15 @@ function make_xlsx_lib(a) {
26277
26279
  });
26278
26280
  return (
26279
26281
  (a.prototype.escapeAttributeValue = function (e) {
26280
- return '"' + e.replace(/\"/g, "&quot;") + '"';
26282
+ return (
26283
+ '"' +
26284
+ e
26285
+ .replace(/&(?!(?:amp|lt|gt|quot|apos|#[0-9]+|#x[0-9a-fA-F]+);)/g, "&amp;")
26286
+ .replace(/</g, "&lt;")
26287
+ .replace(/>/g, "&gt;")
26288
+ .replace(/\"/g, "&quot;") +
26289
+ '"'
26290
+ );
26281
26291
  }),
26282
26292
  (a.prototype.toXml = function (e) {
26283
26293
  // in this function make style tag
@@ -26457,12 +26467,15 @@ function make_xlsx_lib(a) {
26457
26467
  },
26458
26468
  _addFont: function (e) {
26459
26469
  if (!e) return 0;
26470
+ var fontColor;
26460
26471
  var t = vu("font")
26461
26472
  .append(vu("sz").attr("val", e.sz || this.defaultStyle.font.sz))
26462
26473
  .append(
26463
26474
  vu("name").attr("val", e.name || this.defaultStyle.font.name)
26464
26475
  );
26465
26476
  e.bold && t.append(vu("b")),
26477
+ e.family != null && t.append(vu("family").attr("val", e.family)),
26478
+ e.scheme != null && t.append(vu("scheme").attr("val", e.scheme)),
26466
26479
  e.underline && t.append(vu("u")),
26467
26480
  e.italic && t.append(vu("i")),
26468
26481
  e.strike && t.append(vu("strike")),
@@ -26470,12 +26483,12 @@ function make_xlsx_lib(a) {
26470
26483
  e.shadow && t.append(vu("shadow")),
26471
26484
  e.vertAlign && t.append(vu("vertAlign").attr("val", e.vertAlign)),
26472
26485
  e.color &&
26473
- (e.color.theme
26474
- ? (t.append(vu("color").attr("theme", e.color.theme)),
26475
- e.color.tint &&
26476
- t.append(vu("tint").attr("theme", e.color.tint)))
26486
+ (e.color.theme != null
26487
+ ? (fontColor = vu("color").attr("theme", e.color.theme),
26488
+ e.color.tint != null && fontColor.attr("tint", e.color.tint),
26489
+ t.append(fontColor))
26477
26490
  : e.color.rgb &&
26478
- t.append(vu("color").attr("rgb", e.color.rgb))),
26491
+ t.append(vu("color").attr("rgb", e.color.rgb.length === 6 ? "FF" + e.color.rgb : e.color.rgb))),
26479
26492
  this.$fonts.append(t);
26480
26493
  t = this.$fonts.children().length;
26481
26494
  return this.$fonts.attr("count", t), t - 1;
@@ -26499,7 +26512,7 @@ function make_xlsx_lib(a) {
26499
26512
  return this.$numFmts.attr("count", e), r;
26500
26513
  },
26501
26514
  _addFill: function (e) {
26502
- if (!e) return 0;
26515
+ if (!e || (!e.patternType && !e.fgColor && !e.bgColor)) return 0;
26503
26516
  var t,
26504
26517
  r = vu("patternFill").attr("patternType", e.patternType || "solid");
26505
26518
  e.fgColor &&
@@ -26509,7 +26522,7 @@ function make_xlsx_lib(a) {
26509
26522
  (e.fgColor.rgb = "FF" + e.fgColor.rgb),
26510
26523
  t.attr("rgb", e.fgColor.rgb),
26511
26524
  r.append(t))
26512
- : e.fgColor.theme &&
26525
+ : e.fgColor.theme != null &&
26513
26526
  (t.attr("theme", e.fgColor.theme),
26514
26527
  e.fgColor.tint && t.attr("tint", e.fgColor.tint),
26515
26528
  r.append(t)),
@@ -26530,7 +26543,7 @@ function make_xlsx_lib(a) {
26530
26543
  t.color.auto
26531
26544
  ? e.attr("auto", t.color.auto)
26532
26545
  : t.color.rgb
26533
- ? e.attr("rgb", t.color.rgb)
26546
+ ? e.attr("rgb", t.color.rgb.length === 6 ? "FF" + t.color.rgb : t.color.rgb)
26534
26547
  : (t.color.theme || t.color.tint) &&
26535
26548
  (e.attr("theme", t.color.theme || "1"),
26536
26549
  e.attr("tint", t.color.tint || "0")),
package/xlsx.js CHANGED
@@ -14542,15 +14542,11 @@ function safe_format(p, fmtid, fillid, opts, themes, styles, fontid, borderid,cf
14542
14542
  if (p.s.bold) p.s.font = { ...p.s.font, bold: p.s.bold ? true : false, };
14543
14543
  if (p.s.italic) p.s.font = { ...p.s.font, italic: p.s.italic ? true : false, };
14544
14544
  if (p.s.name) p.s.font = { ...p.s.font, name: p.s.name, };
14545
+ if (p.s.family != null) p.s.font = { ...p.s.font, family: p.s.family };
14546
+ if (p.s.scheme != null) p.s.font = { ...p.s.font, scheme: p.s.scheme };
14545
14547
  }
14546
14548
  if(borderid != null) p.s.border = styles.Borders[borderid]
14547
14549
  if(cf != null && cf?.alignment) p.s = {...p.s, alignment:cf.alignment}
14548
- if(p.s && p.t == 'z'){
14549
- p.t = 's';
14550
- // p.z = 'General'
14551
- p.v = ' '
14552
- p.w = ' '
14553
- }
14554
14550
  // console.log('p style',p)
14555
14551
  }
14556
14552
 
@@ -14591,10 +14587,6 @@ var drawingregex = /(<(?:\w:)?drawing[^>]*\/>)|(<(?:\w+:)?drawing[^>]*>([\s\S]*)
14591
14587
  var legacyDrawingregex = /(<(?:\w:)?legacyDrawing[^>]*\/>)|(<(?:\w+:)?legacyDrawing[^>]*>([\s\S]*)<\/(?:\w+:)?legacyDrawing>)/g;
14592
14588
 
14593
14589
 
14594
- paperSize="9"
14595
- scale="91"
14596
- fitToWidth="0"
14597
-
14598
14590
  /* 18.3 Worksheets */
14599
14591
  function parse_ws_xml(data, opts, idx, rels, wb, themes, styles) {
14600
14592
  // console.log('parse_ws_xml')