@xlvane/compat 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { StreamSheetSpec, Workbook as Workbook$1, WorkbookProperties as WorkbookProperties$1 } from "xlvane";
1
+ import { FontStyle, StreamSheetSpec, Workbook as Workbook$1, WorkbookProperties as WorkbookProperties$1 } from "xlvane";
2
2
  /**
3
3
  * A cell location of a defined name, as exceljs keeps it.
4
4
  *
@@ -150,6 +150,8 @@ interface WorkbookMeta {
150
150
  readonly views?: readonly Record<string, unknown>[];
151
151
  /** `workbook.calcProperties`. */
152
152
  readonly calcProperties?: Record<string, unknown>;
153
+ /** The default font (font 0) of a loaded workbook (F4). */
154
+ readonly defaultFont?: FontStyle;
153
155
  }
154
156
  /**
155
157
  * Options of `xlsx.load`, `read` and `readFile`, as exceljs `XlsxReadOptions` (index.d.ts L1460;
@@ -287,6 +289,8 @@ export declare class Workbook {
287
289
  get definedNames(): DefinedNames;
288
290
  /** @internal The document properties and defined names to write, as exceljs `workbook.model`. */
289
291
  _meta(): WorkbookMeta;
292
+ /** @internal The default font (font 0) of the file this workbook was loaded from, written again (F4); `null` for a workbook made in code. */
293
+ _defaultFont: FontStyle | null;
290
294
  /** @internal */
291
295
  get _date1904(): boolean;
292
296
  private _byName;
@@ -405,7 +409,7 @@ interface CsvWriteOptions {
405
409
  map?(value: unknown, index: number): unknown;
406
410
  /** Blank lines for missing rows (default `true`). */
407
411
  includeEmptyRows?: boolean;
408
- /** fast-csv formatter options: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `headers`, `writeHeaders`, `includeEndRowDelimiter`, `writeBOM`. */
412
+ /** fast-csv formatter options: `delimiter`, `rowDelimiter`, `quote`, `escape`, `quoteColumns`, `quoteHeaders`, `headers`, `writeHeaders`, `alwaysWriteHeaders`, `includeEndRowDelimiter`, `writeBOM`, `transform`. */
409
413
  formatterOptions?: Record<string, unknown>;
410
414
  /**
411
415
  * `"escape"` (default): text that starts with `=`, `+`, `-`, `@`, tab, CR, LF or another
@@ -2460,6 +2464,8 @@ export declare class Cell {
2460
2464
  private _moved;
2461
2465
  /** @internal Use `worksheet.getCell()`. */
2462
2466
  constructor(_row: Row, _col: number);
2467
+ /** The style a new cell takes: row style wins over column style, part by part (lib/doc/cell.js L99-119 `_mergeStyle`). */
2468
+ private _rowColumnStyle;
2463
2469
  get worksheet(): Worksheet;
2464
2470
  get row(): number;
2465
2471
  get col(): number;
@@ -2482,7 +2488,12 @@ export declare class Cell {
2482
2488
  * this cell, as exceljs translates it (lib/doc/cell.js `_getTranslatedFormula`; X4-29).
2483
2489
  */
2484
2490
  get formula(): string | undefined;
2485
- /** `FormulaType.Shared` for a `{ sharedFormula }` value, `Master` for other formulas, otherwise `None` (lib/doc/cell.js L807). */
2491
+ /**
2492
+ * `FormulaType.Shared` for a `{ sharedFormula }` value, `Master` for other formulas, and
2493
+ * `undefined` for a cell without a formula and for a merged cell other than the top-left one,
2494
+ * as in exceljs, where only the formula value has a `formulaType` (lib/doc/cell.js L264-266,
2495
+ * L807-815; exceljs's declaration says `FormulaType` all the same).
2496
+ */
2486
2497
  get formulaType(): FormulaType;
2487
2498
  get result(): unknown;
2488
2499
  get numFmt(): string | undefined;
@@ -2559,6 +2570,11 @@ export declare class Cell {
2559
2570
  _merge(master: Cell): void;
2560
2571
  /** @internal */
2561
2572
  _unmerge(): void;
2573
+ /**
2574
+ * @internal As exceljs `cell.unmerge()` (lib/doc/cell.js L177-183): a cell merged into another
2575
+ * one gets the row and column style again, instead of the master's.
2576
+ */
2577
+ _unmergeResetStyle(): void;
2562
2578
  }
2563
2579
  /**
2564
2580
  * A row, as exceljs `Row` (lib/doc/row.js).
@@ -2708,7 +2724,7 @@ export declare class Column {
2708
2724
  equivalentTo(other: Column): boolean;
2709
2725
  get defn(): ColumnDefn;
2710
2726
  set defn(value: ColumnDefn | undefined);
2711
- /** Cell values of this column by row number (index 0 unused). */
2727
+ /** Cell values of this column by row number (index 0 unused); empty cells are left out (lib/doc/column.js L179-187). */
2712
2728
  get values(): CellValue[];
2713
2729
  set values(values: CellValue[]);
2714
2730
  eachCell(options: {
@@ -2913,8 +2929,9 @@ export declare class Worksheet {
2913
2929
  }[], change: SpliceChange): void;
2914
2930
  /**
2915
2931
  * @internal Merges a range without the overlap check of `mergeCells`. Cells keep their own
2916
- * style, except those for which `takeMasterStyle` returns true (cells inserted inside the range),
2917
- * which get a copy of the master's style; style objects are never shared.
2932
+ * style object (the master's, when `mergeCells` shared it with them), except those for which
2933
+ * `takeMasterStyle` returns true (cells inserted inside the range), which get a copy of the
2934
+ * master's style.
2918
2935
  */
2919
2936
  _mergeRange(range: {
2920
2937
  top: number;
package/dist/index.js CHANGED
@@ -773,6 +773,67 @@ const BUILTIN_CODES = /* @__PURE__ */ new Map([
773
773
  ["##0.0E+0", 48],
774
774
  ["@", 49]
775
775
  ]);
776
+ const DAY_MS = 864e5;
777
+ /** exceljs's serial 0 in the 1900 date system (lib/utils/utils.js L55-61: 25569 days before 1970). */
778
+ const EPOCH_1899_12_30 = Date.UTC(1899, 11, 30);
779
+ /** Serial 0 of the core's Transitional 1900 system for 1900-01-01 to 1900-02-28 (D-112). */
780
+ const EPOCH_1899_12_31 = Date.UTC(1899, 11, 31);
781
+ const EPOCH_1904_01_01 = Date.UTC(1904, 0, 1);
782
+ const TIME_1900_01_01 = Date.UTC(1900, 0, 1);
783
+ const MIN_DATE_TIME = (/* @__PURE__ */ new Date(0)).setUTCFullYear(1, 0, 1);
784
+ const MAX_DATE_TIME = Date.UTC(9999, 11, 31, 23, 59, 59, 999);
785
+ /**
786
+ * The serial exceljs writes for a Date from 1899-12-30 to the end of 1899 in the 1900 date system
787
+ * (time-only values such as `new Date(Date.UTC(1899, 11, 30, 13, 20))`): `25569 + ms / 86400000`
788
+ * (lib/utils/utils.js L55-57 `dateToExcel`). The core refuses these dates, so they are written
789
+ * as numbers with the cell's date format. `undefined` for other values.
790
+ */
791
+ function earlyDateSerial(v, date1904) {
792
+ if (date1904 || !(v instanceof Date)) return void 0;
793
+ const t = v.getTime();
794
+ return t >= EPOCH_1899_12_30 && t < TIME_1900_01_01 ? (t - EPOCH_1899_12_30) / DAY_MS : void 0;
795
+ }
796
+ /**
797
+ * exceljs's test for a date format (lib/utils/utils.js L140-151 `isDateFmt`): after removing the
798
+ * parts in brackets and in double quotes, the format has one of the letters y, m, d, h, M, s, b.
799
+ * It also takes upper-case formats such as `DD.MM.YYYY`, which the core does not.
800
+ */
801
+ function isExcelJsDateFormat(fmt) {
802
+ if (!fmt) return false;
803
+ return /[ymdhMsb]/.test(fmt.replace(/\[[^\]]*]/g, "").replace(/"[^"]*"/g, ""));
804
+ }
805
+ /**
806
+ * A serial as a Date, as the core converts it (D-112), except that a serial below 1 in the
807
+ * Transitional 1900 system counts from 1899-12-30 as in exceljs (F6). `null` where the core has no
808
+ * Date either (serial 60, negative serials, outside 0001-9999).
809
+ */
810
+ function serialToDate(serial, dates) {
811
+ if (!Number.isFinite(serial) || Math.abs(serial) > 3e6) return null;
812
+ const ms = Math.round(serial * DAY_MS);
813
+ let time;
814
+ if (dates.date1904) time = EPOCH_1904_01_01 + ms;
815
+ else if (dates.strict) time = EPOCH_1899_12_30 + ms;
816
+ else if (ms < 0) return null;
817
+ else if (ms < DAY_MS) time = EPOCH_1899_12_30 + ms;
818
+ else if (ms < 60 * DAY_MS) time = EPOCH_1899_12_31 + ms;
819
+ else if (ms < 61 * DAY_MS) return null;
820
+ else time = EPOCH_1899_12_30 + ms;
821
+ return time < MIN_DATE_TIME || time > MAX_DATE_TIME ? null : new Date(time);
822
+ }
823
+ /**
824
+ * A plain value read by the core as exceljs gives it: a time-only Date of the Transitional 1900
825
+ * system (which the core places on 1899-12-31, D-112) on 1899-12-30, as exceljs's `excelToDate`
826
+ * (lib/utils/utils.js L58-61) does (F6); and, when `dateFormat` is set, a number whose format
827
+ * exceljs takes for a date (`isExcelJsDateFormat`) as a Date (F7).
828
+ */
829
+ function readValueOut(value, numFmt, dates, dateFormat = true) {
830
+ if (value instanceof Date) {
831
+ const t = value.getTime();
832
+ return !dates.date1904 && !dates.strict && t < TIME_1900_01_01 ? /* @__PURE__ */ new Date(t - DAY_MS) : value;
833
+ }
834
+ if (dateFormat && typeof value === "number" && isExcelJsDateFormat(numFmt)) return serialToDate(value, dates) ?? value;
835
+ return value;
836
+ }
776
837
  function colorIn(c, where) {
777
838
  if (c === void 0 || c === null) return void 0;
778
839
  if (typeof c.argb === "string") return c.argb;
@@ -973,14 +1034,16 @@ function isRecord$1(v) {
973
1034
  return typeof v === "object" && v !== null && !(v instanceof Date) && !Array.isArray(v);
974
1035
  }
975
1036
  /** The `result` part of a formula value for the writer. */
976
- function resultIn(r) {
1037
+ function resultIn(r, date1904 = false) {
977
1038
  if (r === void 0 || r === null) return {};
1039
+ const serial = earlyDateSerial(r, date1904);
1040
+ if (serial !== void 0) return { result: serial };
978
1041
  if (isRecord$1(r) && typeof r["error"] === "string") return { result: { error: r["error"] } };
979
1042
  return { result: r };
980
1043
  }
981
1044
  /** The cached result of a formula as a plain value (`null` without one). */
982
- function resultValue(r) {
983
- return resultIn(r).result ?? null;
1045
+ function resultValue(r, date1904) {
1046
+ return resultIn(r, date1904).result ?? null;
984
1047
  }
985
1048
  /**
986
1049
  * The range of each shared formula of a worksheet, by master address, as exceljs computes it on
@@ -1095,9 +1158,11 @@ function overlapCounts(queries, set) {
1095
1158
  return counts;
1096
1159
  }
1097
1160
  /** Converts a cell value for the writer; a `{ text, hyperlink }` value gives its text (the link is {@link linkIn}). */
1098
- function valueIn(cell, sharedRanges) {
1161
+ function valueIn(cell, sharedRanges, date1904 = false) {
1099
1162
  const v = cell.value;
1100
1163
  if (v === null || v === void 0) return null;
1164
+ const serial = earlyDateSerial(v, date1904);
1165
+ if (serial !== void 0) return serial;
1101
1166
  if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v instanceof Date) return v;
1102
1167
  if (isRecord$1(v)) {
1103
1168
  if (cell.type === ValueType.Hyperlink) {
@@ -1109,19 +1174,19 @@ function valueIn(cell, sharedRanges) {
1109
1174
  const master = v["sharedFormula"].replace(/\$/g, "").toUpperCase();
1110
1175
  if (sharedRanges === void 0 || sharedRanges.has(master)) return {
1111
1176
  sharedFormula: master,
1112
- ...resultIn(v["result"])
1177
+ ...resultIn(v["result"], date1904)
1113
1178
  };
1114
1179
  const text = cell.formula;
1115
1180
  if (text !== void 0) return {
1116
1181
  formula: text,
1117
- ...resultIn(v["result"])
1182
+ ...resultIn(v["result"], date1904)
1118
1183
  };
1119
- return resultValue(v["result"]);
1184
+ return resultValue(v["result"], date1904);
1120
1185
  }
1121
1186
  if (typeof v["formula"] === "string") {
1122
1187
  const base = {
1123
1188
  formula: v["formula"],
1124
- ...resultIn(v["result"])
1189
+ ...resultIn(v["result"], date1904)
1125
1190
  };
1126
1191
  if (v["shareType"] === "array" && typeof v["ref"] === "string") return {
1127
1192
  ...base,
@@ -1379,7 +1444,8 @@ function sameBytes(a, b) {
1379
1444
  function toNewWorkbook(sheets, date1904, meta = {}) {
1380
1445
  const book = createWorkbook({
1381
1446
  ...date1904 ? { dateSystem: "1904" } : {},
1382
- ...meta.properties ? { properties: meta.properties } : {}
1447
+ ...meta.properties ? { properties: meta.properties } : {},
1448
+ ...meta.defaultFont ? { defaultFont: meta.defaultFont } : {}
1383
1449
  });
1384
1450
  if (sheets.length === 0) notSupported("Writing a workbook without worksheets");
1385
1451
  const coreImages = /* @__PURE__ */ new Map();
@@ -1426,19 +1492,46 @@ function toNewWorkbook(sheets, date1904, meta = {}) {
1426
1492
  if (options) sheet.column(column.number, options);
1427
1493
  });
1428
1494
  const styleCache = /* @__PURE__ */ new Map();
1495
+ const partsCache = /* @__PURE__ */ new Map();
1496
+ const byParts = (s) => {
1497
+ const parts = [
1498
+ s.numFmt,
1499
+ s.font,
1500
+ s.fill,
1501
+ s.border,
1502
+ s.alignment,
1503
+ s.protection
1504
+ ];
1505
+ let level = partsCache;
1506
+ for (let i = 0; i < parts.length - 1; i++) {
1507
+ let next = level.get(parts[i]);
1508
+ if (!next) level.set(parts[i], next = /* @__PURE__ */ new Map());
1509
+ level = next;
1510
+ }
1511
+ let c = level.get(parts[parts.length - 1]);
1512
+ if (!c) level.set(parts[parts.length - 1], c = styleIn(s));
1513
+ return c;
1514
+ };
1429
1515
  const styleOf = (s) => {
1430
1516
  let c = styleCache.get(s);
1431
1517
  if (!c) {
1432
- c = styleIn(s);
1518
+ c = byParts(s);
1433
1519
  styleCache.set(s, c);
1434
1520
  }
1435
1521
  return c;
1436
1522
  };
1437
- const styleFor = (cell) => useStyles ? cellStyleIn(cell, styleOf(cell.style)) : isDateCell(cell) ? { numFmt: 14 } : {};
1438
- const uniform = uniformMerges(ws);
1523
+ const styleFor = (cell) => {
1524
+ if (!useStyles) return isDateCell(cell) ? { numFmt: 14 } : {};
1525
+ const master = cell._master;
1526
+ const style = master !== null && cell.style !== master.style && sameStyle(cell.style, master.style) ? master.style : cell.style;
1527
+ return cellStyleIn(cell, styleOf(style));
1528
+ };
1529
+ const blocks = mergeBlocks(ws);
1439
1530
  ws._rows.forEach((row) => {
1440
1531
  if (!row) return;
1441
- if (row.height !== void 0 || row.hidden || row.outlineLevel) sheet.row(row.number, {
1532
+ const style = rowStyleIn(row, useStyles, styleOf);
1533
+ if (row.height !== void 0 || row.hidden || row.outlineLevel || style) sheet.row(row.number, {
1534
+ ...style ? { style } : {},
1442
1535
  ...row.height === void 0 ? {} : { height: row.height },
1443
1536
  ...row.hidden ? { hidden: true } : {},
1444
1537
  ...row.outlineLevel ? { outlineLevel: row.outlineLevel } : {},
@@ -1446,7 +1539,7 @@ function toNewWorkbook(sheets, date1904, meta = {}) {
1446
1539
  });
1447
1540
  row._cells.forEach((cell) => {
1448
1541
  if (!cell) return;
1449
- if (cell._master !== null && uniform.masters.has(cell._master)) {
1542
+ if (cell._master !== null && blocks.covers(cell)) {
1450
1543
  if (cell.note !== void 0) at(ws.name, cell.address, () => {
1451
1544
  const note = noteIn(cell);
1452
1545
  if (note) sheet.note(cell.address, note);
@@ -1454,10 +1547,8 @@ function toNewWorkbook(sheets, date1904, meta = {}) {
1454
1547
  } else at(ws.name, cell.address, () => writeCell(cell));
1455
1548
  });
1456
1549
  });
1457
- for (const m of uniform.ranges) {
1458
- const master = ws.findCell(m.top, m.left);
1459
- if (!master) continue;
1460
- const style = styleFor(master);
1550
+ for (const m of blocks.blocks) {
1551
+ const style = at(ws.name, m.cell.address, () => styleFor(m.cell));
1461
1552
  if (Object.keys(style).length === 0) continue;
1462
1553
  const width = m.right - m.left + 1;
1463
1554
  const step = Math.max(1, Math.floor(MERGE_STYLE_BLOCK / width));
@@ -1470,7 +1561,7 @@ function toNewWorkbook(sheets, date1904, meta = {}) {
1470
1561
  const note = noteIn(cell);
1471
1562
  if (note) sheet.note(cell.address, note);
1472
1563
  const style = styleFor(cell);
1473
- const value = cell.type === ValueType.Merge ? null : valueIn(cell, sharedRanges);
1564
+ const value = cell.type === ValueType.Merge ? null : valueIn(cell, sharedRanges, date1904);
1474
1565
  if (value === null && Object.keys(style).length === 0) return;
1475
1566
  sheet.set(cell.address, value, style);
1476
1567
  const link = cell.type === ValueType.Merge ? null : linkIn(cell);
@@ -1505,53 +1596,125 @@ function toNewWorkbook(sheets, date1904, meta = {}) {
1505
1596
  for (const d of meta.definedNames ?? []) book.defineName(d.name, d.ranges.join(","));
1506
1597
  return book;
1507
1598
  }
1599
+ /**
1600
+ * The part of a file's default font (font 0) that the writer accepts as `defaultFont`: parts it
1601
+ * refuses (a name of more than 31 characters or an empty one, a size outside 1 to 409.55 points)
1602
+ * are left out, so that a loaded workbook can always be written again, as with 1.3.0 and exceljs,
1603
+ * which writes its own Calibri 11 as font 0. `null` when nothing of it is accepted.
1604
+ */
1605
+ function writableDefaultFont(font) {
1606
+ if (!font) return null;
1607
+ const out = { ...font };
1608
+ for (let tries = Object.keys(out).length; tries >= 0; tries--) {
1609
+ if (Object.keys(out).length === 0) return null;
1610
+ try {
1611
+ createWorkbook({ defaultFont: out });
1612
+ return out;
1613
+ } catch (error) {
1614
+ const property = error instanceof XlvError ? error.details["property"] : void 0;
1615
+ const key = typeof property === "string" && property.startsWith("font.") ? property.slice(5).split(".")[0] : void 0;
1616
+ if (error instanceof XlvError && error.code === "XLV_E_INVALID_STYLE" && key !== void 0 && key in out) delete out[key];
1617
+ else return null;
1618
+ }
1619
+ }
1620
+ return null;
1621
+ }
1622
+ /**
1623
+ * The row style to write for a row (F3), as exceljs writes it: row@s with customFormat="1"
1624
+ * (row-xform.js L19-23, L44-47), with styles only, and only for a row that exceljs writes at all,
1625
+ * one with a height or cells (lib/doc/row.js L333-364). `undefined` when there is none.
1626
+ */
1627
+ function rowStyleIn(row, useStyles, convert = styleIn) {
1628
+ if (!useStyles || row.height === void 0 && !row._cells.some((c) => c !== void 0)) return void 0;
1629
+ const style = convert(row.style);
1630
+ return Object.keys(style).length > 0 ? style : void 0;
1631
+ }
1508
1632
  /** Cells styled by one `sheet.style` call for a merged range: at most what the core styles at once. */
1509
1633
  const MERGE_STYLE_BLOCK = 1048576;
1510
- /** Whether two exceljs styles have the same parts (the same objects, as a merge's copies have). */
1511
- function sameStyleParts(a, b) {
1634
+ /**
1635
+ * Whether two exceljs styles are equal: the same object (as `mergeCells` shares the master's),
1636
+ * parts that are the same objects (a column style given to new cells), or equal plain data (styles
1637
+ * read from a file). Parts that are `undefined` count as missing.
1638
+ */
1639
+ function sameStyle(a, b) {
1512
1640
  if (a === b) return true;
1513
- let n = 0;
1514
- for (const k in a) {
1515
- if (a[k] !== b[k]) return false;
1516
- n++;
1517
- }
1518
- for (const _ in b) n--;
1519
- return n === 0;
1641
+ const x = a;
1642
+ const y = b;
1643
+ for (const k in x) if (!samePlain(x[k], y[k], 0)) return false;
1644
+ for (const k in y) if (x[k] === void 0 && y[k] !== void 0) return false;
1645
+ return true;
1646
+ }
1647
+ /** Whether two styles read by the core are the same data (F3, F4: a row or column style against xf 0). */
1648
+ function sameStyleData(a, b) {
1649
+ return samePlain(a, b, 0);
1650
+ }
1651
+ /** Deep equality of plain style data (objects, arrays and primitives), to a bounded depth. */
1652
+ function samePlain(a, b, depth) {
1653
+ if (a === b) return true;
1654
+ if (depth > 8 || a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
1655
+ if (Array.isArray(a) !== Array.isArray(b) || a instanceof Date || b instanceof Date) return false;
1656
+ const x = a;
1657
+ const y = b;
1658
+ const keys = Object.keys(x);
1659
+ if (keys.length !== Object.keys(y).length) return false;
1660
+ for (const k of keys) if (!Object.prototype.hasOwnProperty.call(y, k) || !samePlain(x[k], y[k], depth + 1)) return false;
1661
+ return true;
1520
1662
  }
1521
1663
  /**
1522
- * The merged ranges of a worksheet whose other cells all exist, belong to the range's master and
1523
- * carry the master's style (the copy `mergeCells` gives them, as in exceljs), and the masters all
1524
- * of whose ranges are such. Their cells are written as the master's style over the range (K2),
1525
- * and their notes one by one (K3).
1664
+ * The other cells of the merged ranges, styled in blocks instead of one by one (K2, F2): in each
1665
+ * column of a range, runs of rows whose cells exist, belong to the range's master and have equal
1666
+ * styles (the object `mergeCells` shares with them, as in exceljs, a column's or row's style
1667
+ * parts, or equal data read from a file), joined across columns while the runs are the same
1668
+ * (review of 1.3.1: cells of a styled column or row that differ from the master were written one
1669
+ * by one). The master itself is written as a cell. `covers` tells whether a cell is styled by a
1670
+ * block; only its note is then written on its own (K3).
1526
1671
  */
1527
- function uniformMerges(ws) {
1528
- const ranges = [];
1672
+ function mergeBlocks(ws) {
1673
+ const blocks = [];
1529
1674
  const masters = /* @__PURE__ */ new Set();
1530
- const mixed = /* @__PURE__ */ new Set();
1531
1675
  for (const m of ws._merges.values()) {
1532
1676
  const master = ws.findCell(m.top, m.left);
1533
1677
  if (!master) continue;
1534
- let ok = true;
1535
- for (let r = m.top; ok && r <= m.bottom; r++) {
1536
- const row = ws.findRow(r);
1537
- for (let c = m.left; c <= m.right; c++) {
1538
- if (r === m.top && c === m.left) continue;
1539
- const cell = row?.findCell(c);
1540
- if (!cell || cell._master !== master || !sameStyleParts(cell.style, master.style)) {
1541
- ok = false;
1542
- break;
1678
+ masters.add(master);
1679
+ let open = [];
1680
+ const close = (right) => {
1681
+ for (const o of open) blocks.push({
1682
+ cell: o.run.cell,
1683
+ top: o.run.top,
1684
+ bottom: o.run.bottom,
1685
+ left: o.left,
1686
+ right
1687
+ });
1688
+ };
1689
+ for (let c = m.left; c <= m.right; c++) {
1690
+ const runs = [];
1691
+ let run = null;
1692
+ for (let r = m.top; r <= m.bottom; r++) {
1693
+ const cell = r === m.top && c === m.left ? void 0 : ws.findRow(r)?.findCell(c);
1694
+ if (!cell || cell._master !== master) {
1695
+ run = null;
1696
+ continue;
1543
1697
  }
1698
+ if (run && sameStyle(cell.style, run.cell.style)) run.bottom = r;
1699
+ else runs.push(run = {
1700
+ top: r,
1701
+ bottom: r,
1702
+ cell
1703
+ });
1704
+ }
1705
+ if (!(open.length === runs.length && runs.every((x, i) => x.top === open[i].run.top && x.bottom === open[i].run.bottom && sameStyle(x.cell.style, open[i].run.cell.style)))) {
1706
+ close(c - 1);
1707
+ open = runs.map((x) => ({
1708
+ run: x,
1709
+ left: c
1710
+ }));
1544
1711
  }
1545
1712
  }
1546
- if (ok) {
1547
- ranges.push(m);
1548
- masters.add(master);
1549
- } else mixed.add(master);
1713
+ close(m.right);
1550
1714
  }
1551
- for (const master of mixed) masters.delete(master);
1552
1715
  return {
1553
- ranges: ranges.filter((m) => masters.has(ws.findCell(m.top, m.left))),
1554
- masters
1716
+ blocks,
1717
+ covers: (cell) => cell._master !== null && masters.has(cell._master)
1555
1718
  };
1556
1719
  }
1557
1720
  /**
@@ -3088,10 +3251,13 @@ var Cell = class {
3088
3251
  constructor(_row, _col) {
3089
3252
  this._row = _row;
3090
3253
  this._col = _col;
3091
- const column = _row.worksheet.findColumn(_col);
3092
- this.style = {
3093
- ...column?.style,
3094
- ...withoutEmpty(_row.style)
3254
+ this.style = this._rowColumnStyle();
3255
+ }
3256
+ /** The style a new cell takes: row style wins over column style, part by part (lib/doc/cell.js L99-119 `_mergeStyle`). */
3257
+ _rowColumnStyle() {
3258
+ return {
3259
+ ...this._row.worksheet.findColumn(this._col)?.style,
3260
+ ...withoutEmpty(this._row.style)
3095
3261
  };
3096
3262
  }
3097
3263
  get worksheet() {
@@ -3204,12 +3370,17 @@ var Cell = class {
3204
3370
  return moved;
3205
3371
  }
3206
3372
  }
3207
- /** `FormulaType.Shared` for a `{ sharedFormula }` value, `Master` for other formulas, otherwise `None` (lib/doc/cell.js L807). */
3373
+ /**
3374
+ * `FormulaType.Shared` for a `{ sharedFormula }` value, `Master` for other formulas, and
3375
+ * `undefined` for a cell without a formula and for a merged cell other than the top-left one,
3376
+ * as in exceljs, where only the formula value has a `formulaType` (lib/doc/cell.js L264-266,
3377
+ * L807-815; exceljs's declaration says `FormulaType` all the same).
3378
+ */
3208
3379
  get formulaType() {
3209
- const v = this.value;
3210
- if (!isRecord(v)) return FormulaType.None;
3380
+ const v = this._master ? null : this.value;
3381
+ if (!isRecord(v)) return void 0;
3211
3382
  if (typeof v["sharedFormula"] === "string") return FormulaType.Shared;
3212
- return typeof v["formula"] === "string" ? FormulaType.Master : FormulaType.None;
3383
+ return typeof v["formula"] === "string" ? FormulaType.Master : void 0;
3213
3384
  }
3214
3385
  get result() {
3215
3386
  const v = this.value;
@@ -3384,6 +3555,15 @@ var Cell = class {
3384
3555
  _unmerge() {
3385
3556
  this._master = null;
3386
3557
  }
3558
+ /**
3559
+ * @internal As exceljs `cell.unmerge()` (lib/doc/cell.js L177-183): a cell merged into another
3560
+ * one gets the row and column style again, instead of the master's.
3561
+ */
3562
+ _unmergeResetStyle() {
3563
+ if (this._master === null) return;
3564
+ this._master = null;
3565
+ this.style = this._rowColumnStyle();
3566
+ }
3387
3567
  };
3388
3568
  function withoutEmpty(style) {
3389
3569
  const out = {};
@@ -3761,11 +3941,11 @@ var Column = class {
3761
3941
  this._hidden = !!value.hidden;
3762
3942
  this._outlineLevel = value.outlineLevel ?? 0;
3763
3943
  }
3764
- /** Cell values of this column by row number (index 0 unused). */
3944
+ /** Cell values of this column by row number (index 0 unused); empty cells are left out (lib/doc/column.js L179-187). */
3765
3945
  get values() {
3766
3946
  const out = [];
3767
3947
  this.eachCell((cell, r) => {
3768
- out[r] = cell.value;
3948
+ if (cell.type !== ValueType.Null) out[r] = cell.value;
3769
3949
  });
3770
3950
  return out;
3771
3951
  }
@@ -3780,8 +3960,7 @@ var Column = class {
3780
3960
  const includeEmpty = typeof options === "object" && options !== null && options.includeEmpty === true;
3781
3961
  if (!cb) return;
3782
3962
  this.worksheet.eachRow({ includeEmpty }, (row, r) => {
3783
- const cell = includeEmpty ? row.getCell(this.number) : row.findCell(this.number);
3784
- if (cell && (includeEmpty || cell.type !== ValueType.Null)) cb(cell, r);
3963
+ cb(row.getCell(this.number), r);
3785
3964
  });
3786
3965
  }
3787
3966
  applyStyle(name, value) {
@@ -4344,8 +4523,9 @@ var Worksheet = class {
4344
4523
  }
4345
4524
  /**
4346
4525
  * @internal Merges a range without the overlap check of `mergeCells`. Cells keep their own
4347
- * style, except those for which `takeMasterStyle` returns true (cells inserted inside the range),
4348
- * which get a copy of the master's style; style objects are never shared.
4526
+ * style object (the master's, when `mergeCells` shared it with them), except those for which
4527
+ * `takeMasterStyle` returns true (cells inserted inside the range), which get a copy of the
4528
+ * master's style.
4349
4529
  */
4350
4530
  _mergeRange(range, takeMasterStyle = () => false) {
4351
4531
  const master = this.getCell(range.top, range.left);
@@ -4657,7 +4837,7 @@ var Worksheet = class {
4657
4837
  if (r === range.top && c === range.left) continue;
4658
4838
  const cell = this.getCell(r, c);
4659
4839
  cell._merge(master);
4660
- if (!this._mergeKeepStyle) cell.style = { ...master.style };
4840
+ if (!this._mergeKeepStyle) cell.style = master.style;
4661
4841
  }
4662
4842
  master._mergeCount++;
4663
4843
  this._merges.set(`${columnLetter(range.left)}${String(range.top)}`, range);
@@ -4676,7 +4856,7 @@ var Worksheet = class {
4676
4856
  unMergeCells(...args) {
4677
4857
  const range = parseRangeArgs(args);
4678
4858
  for (const [key, m] of this._merges) if (range.top <= m.bottom && m.top <= range.bottom && range.left <= m.right && m.left <= range.right) {
4679
- for (let r = m.top; r <= m.bottom; r++) for (let c = m.left; c <= m.right; c++) this.findCell(r, c)?._unmerge();
4859
+ for (let r = m.top; r <= m.bottom; r++) for (let c = m.left; c <= m.right; c++) this.findCell(r, c)?._unmergeResetStyle();
4680
4860
  const master = this.findCell(m.top, m.left);
4681
4861
  if (master) master._mergeCount = Math.max(0, master._mergeCount - 1);
4682
4862
  this._merges.delete(key);
@@ -5215,6 +5395,7 @@ function* csvRows(text, o) {
5215
5395
  }
5216
5396
  const rowLine = line;
5217
5397
  const fields = [];
5398
+ let quoted = false;
5218
5399
  let extra = 0;
5219
5400
  let extraText = false;
5220
5401
  const push = (field) => {
@@ -5228,6 +5409,7 @@ function* csvRows(text, o) {
5228
5409
  let start = at;
5229
5410
  while (start < n && (text[start] === " " || text[start] === " ") && text[start] !== o.delimiter) start++;
5230
5411
  if (o.quote !== "" && text[start] === o.quote) {
5412
+ quoted = true;
5231
5413
  let value = "";
5232
5414
  let i = start + 1;
5233
5415
  let from = i;
@@ -5267,7 +5449,7 @@ function* csvRows(text, o) {
5267
5449
  }
5268
5450
  at = skipLineEnd(at);
5269
5451
  line++;
5270
- const row = fields.length === 1 && fields[0].trim() === "" ? [""] : fields;
5452
+ const row = fields.length === 1 && !quoted && fields[0].trim() === "" ? [] : fields;
5271
5453
  if (o.ignoreEmpty && !extraText && row.every((f) => f.trim() === "")) continue;
5272
5454
  kept++;
5273
5455
  if (o.skipHeader && kept === 1) continue;
@@ -5319,13 +5501,24 @@ function formatterOptions(raw, escapeFormulas) {
5319
5501
  const quote = q === false ? "" : q === true || q === void 0 ? "\"" : String(q);
5320
5502
  const escape = typeof o["escape"] === "string" ? o["escape"] : quote;
5321
5503
  const escapeRe = (s) => s.replace(/[\\^$.*+?()[\]{}|-]/g, "\\$&");
5504
+ const transform = o["transform"];
5505
+ if (transform && typeof transform !== "function") throw invalid("formatterOptions.transform", "The option \"transform\" must be a function.", "Pass a function that takes a row and returns the row to write.");
5506
+ if (o["BOM"] !== void 0 && o["BOM"] !== "") throw new XlvError("XLV_E_NOT_SUPPORTED", "formatterOptions.BOM other than \"\\uFEFF\" is not supported.", {
5507
+ hint: "Leave BOM out; writeBOM: true writes the byte order mark U+FEFF.",
5508
+ details: { option: "BOM" }
5509
+ });
5510
+ const quoteColumns = o["quoteColumns"] ?? false;
5322
5511
  return {
5323
5512
  delimiter,
5324
5513
  rowDelimiter,
5325
5514
  quote,
5326
5515
  escapedQuote: `${escape}${quote}`,
5327
- quoteColumns: o["quoteColumns"] ?? false,
5328
- headers: Array.isArray(o["headers"]) && o["writeHeaders"] !== false ? o["headers"].map(String) : null,
5516
+ quoteColumns,
5517
+ quoteHeaders: o["quoteHeaders"] === void 0 ? quoteColumns : o["quoteHeaders"],
5518
+ headers: Array.isArray(o["headers"]) ? o["headers"].map(String) : null,
5519
+ shouldWriteHeaders: !!o["headers"] && !!(o["writeHeaders"] ?? true),
5520
+ alwaysWriteHeaders: o["alwaysWriteHeaders"] === true,
5521
+ transform: typeof transform === "function" ? transform : void 0,
5329
5522
  includeEndRowDelimiter: o["includeEndRowDelimiter"] === true,
5330
5523
  writeBOM: o["writeBOM"] === true,
5331
5524
  escapeChars: new RegExp(`[${escapeRe(delimiter)}${escapeRe(rowDelimiter)}|\\r|\\n${escapeFormulas ? escapeRe(COMMON_DELIMITERS) : ""}]`),
@@ -5349,8 +5542,8 @@ function splitStartsFormula(text) {
5349
5542
  }
5350
5543
  return false;
5351
5544
  }
5352
- /** One field as fast-csv FieldFormatter.format writes it. */
5353
- function formatField(value, index, f, forceQuote) {
5545
+ /** One field as fast-csv FieldFormatter.format writes it; `quoteThis`: quoteColumns or quoteHeaders asks for quotes, or the escape. */
5546
+ function formatField(value, index, f, quoteThis) {
5354
5547
  const text = `${value === null || value === void 0 ? "" : String(value)}`.replace(/\0/g, "");
5355
5548
  if (f.quote === "" && (/[\r\n]/.test(text) || f.delimiter !== "" && text.includes(f.delimiter))) throw new XlvError("XLV_E_INVALID_OPTION", `Field ${String(index + 1)} contains a line break or the delimiter, which cannot be written with quote: false.`, {
5356
5549
  hint: "Leave formatterOptions.quote at its default (\") so such fields are quoted, or remove the line breaks from the value.",
@@ -5367,8 +5560,6 @@ function formatField(value, index, f, forceQuote) {
5367
5560
  }
5368
5561
  });
5369
5562
  if (f.quote !== "" && text.includes(f.quote)) return `${f.quote}${text.split(f.quote).join(f.escapedQuote)}${f.quote}`;
5370
- const q = f.quoteColumns;
5371
- const quoteThis = forceQuote || q === true || Array.isArray(q) && q[index] === true;
5372
5563
  if (text.search(f.escapeChars) !== -1 || quoteThis) return `${f.quote}${text}${f.quote}`;
5373
5564
  return text;
5374
5565
  }
@@ -5551,7 +5742,7 @@ function columnName(n) {
5551
5742
  *
5552
5743
  * @internal
5553
5744
  */
5554
- function worksheetCsv(sheet, options, encoding = "utf8") {
5745
+ async function worksheetCsv(sheet, options, encoding = "utf8") {
5555
5746
  const injection = options.formulaInjection ?? "escape";
5556
5747
  if (injection !== "escape" && injection !== "off") throw invalid("formulaInjection", "The option \"formulaInjection\" must be \"escape\" or \"off\".", "Use \"escape\" (default) or \"off\".");
5557
5748
  const f = formatterOptions(options.formatterOptions, injection === "escape");
@@ -5641,29 +5832,87 @@ function worksheetCsv(sheet, options, encoding = "utf8") {
5641
5832
  if (first === void 0) return [out, false];
5642
5833
  return INJECTION_START.has(first) || first.charCodeAt(0) < 32 || encodedStartIsUnsafe(text, encoding, atStart && f.writeBOM && latin1 ? [255] : []) || utf16 && utf16StartsFormula(text) ? [`'${out}`, f.quote !== ""] : [out, false];
5643
5834
  };
5644
- if (f.headers) lines.push(f.headers.map((h, i) => {
5645
- const [out, force] = escape(h, i === 0);
5646
- return field(out, i, force, 0, i === 0);
5647
- }).join(f.delimiter));
5835
+ /** RowFormatter `headers`: given, or taken from the first row written (L109-124). */
5836
+ let headers = f.headers;
5837
+ /** FieldFormatter `_headers`, by which an object of quoteColumns or quoteHeaders is looked up. */
5838
+ let fieldHeaders = f.headers;
5839
+ let hasWrittenHeaders = false;
5840
+ const own = (o, key) => Object.prototype.hasOwnProperty.call(o, key);
5841
+ const shouldQuote = (index, isHeader) => {
5842
+ const config = isHeader ? f.quoteHeaders : f.quoteColumns;
5843
+ if (typeof config === "boolean") return config;
5844
+ if (Array.isArray(config)) return Boolean(config[index]);
5845
+ if (fieldHeaders !== null && config !== null && typeof config === "object") {
5846
+ const key = String(fieldHeaders[index]);
5847
+ return own(config, key) && Boolean(config[key]);
5848
+ }
5849
+ return false;
5850
+ };
5851
+ const formatColumns = (columns, isHeader, rowNumber) => {
5852
+ const fields = [];
5853
+ for (let i = 0; i < columns.length; i++) {
5854
+ if (!(i in columns)) {
5855
+ fields.push("");
5856
+ continue;
5857
+ }
5858
+ const atStart = lines.length === 0 && i === 0;
5859
+ const [out, force] = escape(columns[i], atStart);
5860
+ fields.push(field(out, i, force || shouldQuote(i, isHeader), isHeader ? 0 : rowNumber, atStart));
5861
+ }
5862
+ lines.push(fields.join(f.delimiter));
5863
+ };
5864
+ const isHashArray = (row) => Array.isArray(row) && Array.isArray(row[0]) && row[0].length === 2;
5865
+ const gatherHeaders = (row) => isHashArray(row) ? row.map((it) => it[0]) : Array.isArray(row) ? row : Object.keys(Object(row));
5866
+ const gatherColumns = (row, names) => {
5867
+ if (!Array.isArray(row)) {
5868
+ const o = Object(row);
5869
+ return names.map((h) => own(o, String(h)) ? o[String(h)] : void 0);
5870
+ }
5871
+ if (isHashArray(row)) return names.map((_h, i) => row[i] ? row[i][1] : "");
5872
+ if (!f.shouldWriteHeaders) return row;
5873
+ return names.map((_h, i) => row[i]);
5874
+ };
5875
+ const formatRow = (row, rowNumber) => {
5876
+ if (!row) return;
5877
+ let formatRowColumns = true;
5878
+ if (headers === null) {
5879
+ headers = gatherHeaders(row);
5880
+ fieldHeaders = headers;
5881
+ if (f.shouldWriteHeaders) formatRowColumns = !(Array.isArray(row) && !isHashArray(row));
5882
+ }
5883
+ if (f.shouldWriteHeaders && !hasWrittenHeaders) {
5884
+ formatColumns(headers, true, 0);
5885
+ hasWrittenHeaders = true;
5886
+ }
5887
+ if (formatRowColumns) formatColumns(gatherColumns(row, headers), false, rowNumber);
5888
+ };
5889
+ const transform = f.transform;
5890
+ const transformed = (row) => {
5891
+ if (!transform) return row;
5892
+ if (transform.length === 1) return transform(row);
5893
+ return new Promise((resolve, reject) => {
5894
+ transform(row, (error, out) => error ? reject(error instanceof Error ? error : new Error(String(error))) : resolve(out));
5895
+ });
5896
+ };
5648
5897
  if (sheet) {
5649
- let lastRow = 0;
5898
+ const rows = [];
5650
5899
  sheet.eachRow((row, rowNumber) => {
5651
- if (includeEmptyRows) while (++lastRow < rowNumber) lines.push("");
5652
- let values = row.values.slice(1);
5653
- if (f.headers) values = f.headers.map((_h, i) => values[i]);
5654
- const fields = [];
5655
- for (let i = 0; i < values.length; i++) {
5656
- if (!(i in values)) {
5657
- fields.push("");
5658
- continue;
5659
- }
5660
- const atStart = lines.length === 0 && i === 0;
5661
- const [out, force] = escape(map(values[i], i), atStart);
5662
- fields.push(field(out, i, force, rowNumber, atStart));
5900
+ rows.push([rowNumber, row]);
5901
+ });
5902
+ let lastRow = f.shouldWriteHeaders && f.headers === null && rows.length > 0 ? rows[0][0] - 1 : 0;
5903
+ for (const [rowNumber, row] of rows) {
5904
+ if (includeEmptyRows) while (++lastRow < rowNumber) {
5905
+ const out = transformed([]);
5906
+ formatRow(out instanceof Promise ? await out : out, lastRow);
5663
5907
  }
5664
- lines.push(fields.join(f.delimiter));
5908
+ const out = transformed(row.values.slice(1).map((v, i) => map(v, i)));
5909
+ formatRow(out instanceof Promise ? await out : out, rowNumber);
5665
5910
  lastRow = rowNumber;
5666
- });
5911
+ }
5912
+ }
5913
+ if (f.alwaysWriteHeaders && lines.length === 0) {
5914
+ if (!headers) throw invalid("formatterOptions.alwaysWriteHeaders", "alwaysWriteHeaders is set, but no header names are given in formatterOptions.headers.", "Pass formatterOptions.headers as an array of names, or leave alwaysWriteHeaders out.");
5915
+ formatColumns(headers, true, 0);
5667
5916
  }
5668
5917
  let text = lines.join(f.rowDelimiter);
5669
5918
  if (f.includeEndRowDelimiter) text += f.rowDelimiter;
@@ -5688,9 +5937,22 @@ function encode(text, encoding) {
5688
5937
  }
5689
5938
  throw invalid("encoding", `The encoding "${encoding ?? ""}" is not supported.`, "Use \"utf8\" (default), \"utf16le\" or \"latin1\".");
5690
5939
  }
5940
+ /**
5941
+ * Node.js's 'latin1' (alias 'binary'), which fast-csv's StringDecoder uses
5942
+ * (`@fast-csv/parse/build/src/CsvParserStream.js` L20): each byte is the code point of the same
5943
+ * value. The WHATWG label 'latin1' of `TextDecoder` is windows-1252 instead (bytes 80-9F become
5944
+ * other characters, for example 80 the euro sign), so it is not used (F10).
5945
+ */
5946
+ const latin1Decoder = { decode(input) {
5947
+ if (!input) return "";
5948
+ let out = "";
5949
+ for (let i = 0; i < input.length; i += 8192) out += String.fromCharCode(...input.subarray(i, i + 8192));
5950
+ return out;
5951
+ } };
5691
5952
  function decoderFor(encoding) {
5692
5953
  const e = encoding.toLowerCase();
5693
- const label = e === "utf8" ? "utf-8" : e === "ucs2" || e === "ucs-2" || e === "utf16le" ? "utf-16le" : e === "binary" ? "latin1" : e;
5954
+ if (e === "latin1" || e === "binary") return latin1Decoder;
5955
+ const label = e === "utf8" ? "utf-8" : e === "ucs2" || e === "ucs-2" || e === "utf16le" ? "utf-16le" : e;
5694
5956
  try {
5695
5957
  return new TextDecoder(label);
5696
5958
  } catch {
@@ -5842,11 +6104,11 @@ var CsvIO = class {
5842
6104
  }
5843
6105
  /** Writes a worksheet as CSV to a stream and ends it. */
5844
6106
  async write(stream, options = {}) {
5845
- await writeTo(stream, encode(this.text(options), "utf8"));
6107
+ await writeTo(stream, encode(await this.text(options), "utf8"));
5846
6108
  }
5847
6109
  /** Writes a worksheet to a CSV file (Node.js, Bun, Deno), replacing it. */
5848
6110
  async writeFile(path, options = {}) {
5849
- const bytes = encode(this.text(options, outputEncoding(options.encoding)), options.encoding);
6111
+ const bytes = encode(await this.text(options, outputEncoding(options.encoding)), options.encoding);
5850
6112
  const fs = await nodeFs("writeFile");
5851
6113
  try {
5852
6114
  await fs.writeFile(path, bytes);
@@ -5864,13 +6126,13 @@ var CsvIO = class {
5864
6126
  }
5865
6127
  /** The CSV of a worksheet as bytes (a Buffer in Node.js), UTF-8 encoded. */
5866
6128
  async writeBuffer(options = {}) {
5867
- return asBuffer$1(encode(this.text(options), "utf8"));
6129
+ return asBuffer$1(encode(await this.text(options), "utf8"));
5868
6130
  }
5869
6131
  /** Removed in exceljs 4 (it throws there too); use `read`. */
5870
6132
  createInputStream() {
5871
6133
  throw new Error("`CSV#createInputStream` is deprecated. You should use `CSV#read` instead. This method will be removed in version 5.0. Please follow upgrade instruction: https://github.com/exceljs/exceljs/blob/master/UPGRADE-4.0.md");
5872
6134
  }
5873
- text(options, encoding = "utf8") {
6135
+ async text(options, encoding = "utf8") {
5874
6136
  const key = options.sheetName || options.sheetId;
5875
6137
  const sheet = this.workbook.getWorksheet(key);
5876
6138
  if (key !== void 0 && !sheet) throw new XlvError("XLV_E_SHEET_NOT_FOUND", `The workbook has no worksheet ${typeof key === "string" ? `named "${key}"` : `with id ${String(key)}`}.`, {
@@ -6407,9 +6669,11 @@ var XlsxIO = class {
6407
6669
  }
6408
6670
  book(options) {
6409
6671
  const useStyles = options?.useStyles !== false;
6672
+ const font = this.workbook._defaultFont;
6410
6673
  return toNewWorkbook(this.workbook.worksheets, this.workbook._date1904, {
6411
6674
  ...this.workbook._meta(),
6412
- useStyles
6675
+ useStyles,
6676
+ ...font ? { defaultFont: font } : {}
6413
6677
  });
6414
6678
  }
6415
6679
  bytes(options) {
@@ -6500,6 +6764,8 @@ var Workbook = class {
6500
6764
  calcProperties: this.calcProperties
6501
6765
  };
6502
6766
  }
6767
+ /** @internal The default font (font 0) of the file this workbook was loaded from, written again (F4); `null` for a workbook made in code. */
6768
+ _defaultFont = null;
6503
6769
  /** @internal */
6504
6770
  get _date1904() {
6505
6771
  return this.properties.date1904 === true;
@@ -6727,6 +6993,10 @@ var Workbook = class {
6727
6993
  const use = (node) => !ignore.has(node);
6728
6994
  let cellsRead = 0;
6729
6995
  for (const s of read.sheets) cellsRead += s.cellCount;
6996
+ const dates = {
6997
+ date1904: read.dateSystem === "1904",
6998
+ strict: read.conformance === "strict"
6999
+ };
6730
7000
  const budget = Math.max(DEFAULT_READ_LIMITS.maxCells, 16 * cellsRead);
6731
7001
  let spent = 0;
6732
7002
  const spend = (positions, what, sheet) => {
@@ -6783,6 +7053,27 @@ var Workbook = class {
6783
7053
  }
6784
7054
  }
6785
7055
  this._clearSheets();
7056
+ const font0 = writableDefaultFont(read.defaultFont);
7057
+ this._defaultFont = font0;
7058
+ const fileStyle = (style, numFmt, styled = Object.keys(style).length > 0) => styleOut(styled && font0 && !style.font ? {
7059
+ ...style,
7060
+ font: font0
7061
+ } : style, numFmt);
7062
+ const lineFormat = (style) => typeof style.numFmt === "string" ? style.numFmt : null;
7063
+ let xf0;
7064
+ const defaultXf = () => {
7065
+ if (xf0 !== void 0) return xf0;
7066
+ xf0 = null;
7067
+ for (const sheet of read.sheets) {
7068
+ if (sheet.kind !== "worksheet") continue;
7069
+ for (const cell of sheet.cells()) if (cell.styleIndex === 0) return xf0 = cell.style;
7070
+ }
7071
+ return xf0;
7072
+ };
7073
+ const lineStyle = (style) => {
7074
+ const zero = defaultXf();
7075
+ return zero && sameStyleData(style, zero) ? {} : fileStyle(style, lineFormat(style));
7076
+ };
6786
7077
  if (read.dateSystem === "1904") this.properties.date1904 = true;
6787
7078
  else delete this.properties.date1904;
6788
7079
  const p = read.properties;
@@ -6844,7 +7135,7 @@ var Workbook = class {
6844
7135
  if (c.width !== null) column.width = c.width;
6845
7136
  if (c.hidden) column.hidden = true;
6846
7137
  if (c.outlineLevel) column.outlineLevel = c.outlineLevel;
6847
- if (c.style) column.style = styleOut(c.style, typeof c.style.numFmt === "string" ? c.style.numFmt : null);
7138
+ if (c.style) column.style = lineStyle(c.style);
6848
7139
  }
6849
7140
  }
6850
7141
  if (use("sheetFormatPr") || use("sheetPr")) {
@@ -6864,13 +7155,13 @@ var Workbook = class {
6864
7155
  }
6865
7156
  let lastRow = 0;
6866
7157
  for (const cell of use("sheetData") ? s.styledCells() : []) {
6867
- ws.getCell(cell.row, cell.col).style = styleOut(cell.style, cell.numFmt);
7158
+ ws.getCell(cell.row, cell.col).style = fileStyle(cell.style, cell.numFmt, cell.styleIndex > 0);
6868
7159
  if (cell.row > lastRow) lastRow = cell.row;
6869
7160
  }
6870
7161
  for (const cell of use("sheetData") ? s.cells() : []) {
6871
7162
  const target = ws.getCell(cell.row, cell.col);
6872
7163
  let value;
6873
- const plain = cell.type === "error" ? { error: cell.value } : cell.value;
7164
+ const plain = cell.type === "error" ? { error: cell.value } : readValueOut(cell.value, cell.numFmt, dates);
6874
7165
  if (cell.formula && hasFormulaText(cell.formula)) value = formulaOut(cell.formula, plain, cell.ref, masters);
6875
7166
  else value = cell.richText ? richTextOut(cell.richText) : plain;
6876
7167
  const h = cell.hyperlink;
@@ -6887,7 +7178,7 @@ var Workbook = class {
6887
7178
  };
6888
7179
  }
6889
7180
  target.value = value;
6890
- target.style = styleOut(cell.style, cell.numFmt);
7181
+ target.style = fileStyle(cell.style, cell.numFmt, cell.styleIndex > 0);
6891
7182
  if (cell.row > lastRow) lastRow = cell.row;
6892
7183
  }
6893
7184
  const dim = /(\d+)$/.exec(s.dimension ?? "");
@@ -6905,6 +7196,7 @@ var Workbook = class {
6905
7196
  const info = s.rowInfo(r);
6906
7197
  if (!info) continue;
6907
7198
  const row = ws.getRow(r);
7199
+ if (info.style && use("sheetData")) row.style = lineStyle(info.style);
6908
7200
  if (info.height !== null) row.height = Math.min(info.height, 409.5);
6909
7201
  if (info.hidden) row.hidden = true;
6910
7202
  if (info.outlineLevel) row.outlineLevel = info.outlineLevel;
@@ -6915,7 +7207,7 @@ var Workbook = class {
6915
7207
  spend(areaOf(m), "A merged range", s.name);
6916
7208
  spendMerge(areaOf(m), m, s.name);
6917
7209
  try {
6918
- ws.mergeCells(m);
7210
+ ws.mergeCellsWithoutStyle(m);
6919
7211
  } catch (error) {
6920
7212
  if (error instanceof XlvError || !(error instanceof Error) || error.message !== "Cannot merge already merged cells") throw error;
6921
7213
  const other = mergeOverlapping(ws, m);
@@ -7281,8 +7573,10 @@ var WorksheetWriter = class extends Worksheet {
7281
7573
  if (styles[i] !== void 0) styled = true;
7282
7574
  });
7283
7575
  for (let i = 0; i < values.length; i++) if (values[i] === void 0) values[i] = null;
7576
+ const rowStyle = rowStyleIn(row, this.writer._useStyles);
7284
7577
  this.rowsOut.push({
7285
7578
  values,
7579
+ ...rowStyle ? { rowStyle } : {},
7286
7580
  ...styled ? { styles } : {},
7287
7581
  ...links.length > 0 ? { hyperlinks: links } : {},
7288
7582
  ...notes.length > 0 ? { notes } : {},
@@ -7727,6 +8021,10 @@ var WorkbookReader = class extends Emitter {
7727
8021
  async *parse(input, options) {
7728
8022
  if (options) this.options = readerOptions(options);
7729
8023
  const book = await this.open(input ?? this.input);
8024
+ const dates = {
8025
+ date1904: book.dateSystem === "1904",
8026
+ strict: book.conformance === "strict"
8027
+ };
7730
8028
  if (this.options.entries === "emit") this.emit("entry", { type: "workbook" });
7731
8029
  if (this.options.worksheets === "ignore") return;
7732
8030
  const host = new Workbook();
@@ -7747,8 +8045,9 @@ var WorkbookReader = class extends Emitter {
7747
8045
  const rows = async function* () {
7748
8046
  for await (const r of book.rows(name)) {
7749
8047
  const row = sheet.getRow(r.number);
8048
+ if (styles && r.style) row.style = styleOut(r.style, typeof r.style.numFmt === "string" ? r.style.numFmt : null);
7750
8049
  for (const cell of r.cells) {
7751
- const plain = cell.type === "error" ? { error: cell.value } : cell.value;
8050
+ const plain = cell.type === "error" ? { error: cell.value } : readValueOut(cell.value, cell.numFmt, dates, !cell.formula);
7752
8051
  const target = row.getCell(cell.col);
7753
8052
  const f = cell.formula;
7754
8053
  const result = plain === null ? {} : { result: plain };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xlvane/compat",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "The commonly used exceljs 4.4.0 API on top of xlvane, for switching with one line in package.json.",
5
5
  "keywords": [
6
6
  "xlsx",
@@ -72,7 +72,7 @@
72
72
  "build": "tsdown"
73
73
  },
74
74
  "dependencies": {
75
- "xlvane": "^1.3.0"
75
+ "xlvane": "^1.3.1"
76
76
  },
77
77
  "publishConfig": {
78
78
  "access": "public",