@ricsam/excel-to-formula-engine 0.2.1 → 0.2.2

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
@@ -150,6 +150,29 @@ await excelToFormulaEngine(file, {
150
150
  | Number formats, hyperlinks | Cell metadata | Stored for your UI; the engine does not interpret them. |
151
151
  | Merges, frozen panes, column widths, hidden sheets | Sheet metadata | Same — stored for your UI to apply. |
152
152
 
153
+ ### Effective cell fonts
154
+
155
+ Existing worksheet cells resolve their formatting in this order: explicit cell
156
+ `s` (including `s="0"`), row `s` when `customFormat` is enabled, column `style`,
157
+ then workbook cell XF 0. Column ranges remain sparse, including full-sheet
158
+ ranges. Named-style font inheritance (`cellStyleXfs` / `xfId`) is resolved;
159
+ `applyFont="0"` selects the base font, while an explicit applied font replaces
160
+ it as a unit. Supported font decorations include explicit false values so a
161
+ plain font can clear inherited bold, italic, or underline.
162
+
163
+ The resolved XF 0 is also retained as `workbookMetadata.defaultCellStyle`
164
+ (engine units: font sizes are pixels). This is additive metadata for clients;
165
+ actual imported cells receive coalesced direct styles without requiring a new
166
+ engine API. `styles:false` disables both. Empty coordinates not present in the
167
+ worksheet are not materialized. Older imported snapshots do not retain XF 0
168
+ and cannot reconstruct it losslessly from neighboring styles.
169
+
170
+ Literal Excel font names are retained as `fontFamily` (clients must provide
171
+ those fonts). This is not full Excel rendering fidelity: theme font substitution,
172
+ table style themes, rich-text runs, and named-style inheritance of non-font
173
+ properties are not translated by this change. Double/accounting underline
174
+ variants become a single boolean underline.
175
+
153
176
  ### Formulas the engine cannot evaluate
154
177
 
155
178
  The engine implements around 40 functions. A formula using anything else — an
package/dist/index.cjs CHANGED
@@ -280,6 +280,11 @@ function parseWorksheet(pkg, partPath, sharedStrings) {
280
280
  function parseCells(worksheet, sharedStrings, result) {
281
281
  const sheetData = worksheet["sheetData"];
282
282
  const shared = new Map;
283
+ const columnStyles = asArray(worksheet["cols"]?.["col"]).map((col) => ({
284
+ min: numAttr(col, "min"),
285
+ max: numAttr(col, "max"),
286
+ style: numAttr(col, "style")
287
+ })).filter((col) => col.min !== undefined && col.max !== undefined && col.style !== undefined);
283
288
  for (const rawRow of asArray(sheetData?.["row"])) {
284
289
  const row = rawRow;
285
290
  const rowNumber = numAttr(row, "r");
@@ -297,11 +302,14 @@ function parseCells(worksheet, sharedStrings, result) {
297
302
  if (!address) {
298
303
  continue;
299
304
  }
300
- const styleIndex = numAttr(cell, "s");
305
+ const explicitStyle = numAttr(cell, "s");
306
+ const rowStyle = boolAttr(row, "customFormat") ? numAttr(row, "s") : undefined;
307
+ const columnStyle = columnStyles.findLast((col) => address.colIndex + 1 >= col.min && address.colIndex + 1 <= col.max)?.style;
308
+ const styleIndex = explicitStyle ?? rowStyle ?? columnStyle ?? 0;
301
309
  const type = attr(cell, "t") ?? "n";
302
310
  const formula = readFormula(cell, address, shared);
303
311
  const { kind, value } = readValue(cell, type, sharedStrings);
304
- if (kind === "empty" && formula === undefined && styleIndex === undefined) {
312
+ if (kind === "empty" && formula === undefined && explicitStyle === undefined && rowStyle === undefined && columnStyle === undefined) {
305
313
  continue;
306
314
  }
307
315
  result.cells.push({
@@ -691,14 +699,19 @@ function parseStyles(pkg) {
691
699
  const fills = asArray(styleSheet["fills"]?.["fill"]);
692
700
  const borders = asArray(styleSheet["borders"]?.["border"]);
693
701
  const cellXfs = asArray(styleSheet["cellXfs"]?.["xf"]);
702
+ const styleXfs = asArray(styleSheet["cellStyleXfs"]?.["xf"]);
694
703
  const formats = cellXfs.map((rawXf) => {
695
704
  const xf = rawXf;
696
705
  const style = {};
697
- if (boolAttr(xf, "applyFont", true)) {
698
- const font = fonts[numAttr(xf, "fontId") ?? -1];
699
- if (font) {
700
- applyFont(style, font, theme, indexedColors);
701
- }
706
+ const baseXf = styleXfs[numAttr(xf, "xfId") ?? -1];
707
+ const baseFontId = boolAttr(baseXf, "applyFont", true) ? numAttr(baseXf, "fontId") ?? 0 : 0;
708
+ const fontId = boolAttr(xf, "applyFont", true) ? numAttr(xf, "fontId") ?? baseFontId : baseFontId;
709
+ const font = fonts[fontId];
710
+ if (font) {
711
+ style.bold = false;
712
+ style.italic = false;
713
+ style.underline = false;
714
+ applyFont(style, font, theme, indexedColors);
702
715
  }
703
716
  if (boolAttr(xf, "applyFill", true)) {
704
717
  const fill = fills[numAttr(xf, "fillId") ?? -1];
@@ -743,14 +756,18 @@ function parseStyles(pkg) {
743
756
  return { formats, differentialFormats };
744
757
  }
745
758
  function applyFont(style, font, theme, indexedColors) {
746
- if (font["b"] !== undefined && isOn(font["b"])) {
747
- style.bold = true;
759
+ const fontFamily = attr(font["name"], "val");
760
+ if (fontFamily) {
761
+ style.fontFamily = fontFamily;
762
+ }
763
+ if (font["b"] !== undefined) {
764
+ style.bold = isOn(font["b"]);
748
765
  }
749
- if (font["i"] !== undefined && isOn(font["i"])) {
750
- style.italic = true;
766
+ if (font["i"] !== undefined) {
767
+ style.italic = isOn(font["i"]);
751
768
  }
752
- if (font["u"] !== undefined && isOn(font["u"])) {
753
- style.underline = true;
769
+ if (font["u"] !== undefined) {
770
+ style.underline = attr(font["u"], "val") !== "none";
754
771
  }
755
772
  const size = numAttr(font["sz"], "val");
756
773
  if (size !== undefined) {
@@ -1441,6 +1458,7 @@ async function convertWorkbook(source, options = {}) {
1441
1458
  cellStyles: cellStyles.length > 0 ? cellStyles : undefined,
1442
1459
  conditionalStyles: conditionalStyles.length > 0 ? conditionalStyles : undefined,
1443
1460
  workbookMetadata: {
1461
+ defaultCellStyle: importStyles ? styleTable.formats[0]?.style : undefined,
1444
1462
  fileName: options.fileName,
1445
1463
  sheetOrder: sheetEntries.map((entry) => entry.name),
1446
1464
  diagnostics: diagnostics.length > 0 ? diagnostics : undefined
@@ -1900,4 +1918,4 @@ function withCsvFileName(source, options) {
1900
1918
  };
1901
1919
  }
1902
1920
 
1903
- //# debugId=5AA8F8A121F02B2D64756E2164756E21
1921
+ //# debugId=361575F73A84A17764756E2164756E21