@ricsam/excel-to-formula-engine 0.2.0 → 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 +23 -0
- package/dist/index.cjs +43 -14
- package/dist/index.cjs.map +6 -6
- package/dist/index.mjs +43 -14
- package/dist/index.mjs.map +6 -6
- package/dist/types/src/types.d.ts +6 -1
- package/package.json +3 -2
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
|
|
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 &&
|
|
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
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
747
|
-
|
|
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
|
|
750
|
-
style.italic =
|
|
766
|
+
if (font["i"] !== undefined) {
|
|
767
|
+
style.italic = isOn(font["i"]);
|
|
751
768
|
}
|
|
752
|
-
if (font["u"] !== undefined
|
|
753
|
-
style.underline =
|
|
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
|
|
@@ -1862,11 +1880,22 @@ async function spreadsheetToFormulaEngine(source, options) {
|
|
|
1862
1880
|
}
|
|
1863
1881
|
async function spreadsheetToFormulaEngineDetailed(source, options) {
|
|
1864
1882
|
const bytes = await toBytes2(source);
|
|
1883
|
+
const fileName = options?.fileName ?? (typeof File !== "undefined" && source instanceof File ? source.name : undefined);
|
|
1865
1884
|
if (looksLikeZip(bytes)) {
|
|
1866
1885
|
return convertWorkbook(bytes, withFileName(source, options));
|
|
1867
1886
|
}
|
|
1887
|
+
if (fileName && /\.(xlsx|xlsm)$/i.test(fileName)) {
|
|
1888
|
+
throw new Error(`${fileName} is named as an Excel workbook but is not one — the file is ` + `corrupt, or it is a legacy .xls saved under the wrong extension.`);
|
|
1889
|
+
}
|
|
1890
|
+
assertLooksLikeText(bytes, fileName);
|
|
1868
1891
|
return csvToFormulaEngineDetailed(bytes, withCsvFileName(source, options));
|
|
1869
1892
|
}
|
|
1893
|
+
function assertLooksLikeText(bytes, fileName) {
|
|
1894
|
+
const sample = bytes.subarray(0, 8192);
|
|
1895
|
+
if (sample.indexOf(0) !== -1) {
|
|
1896
|
+
throw new Error(`${fileName ?? "The file"} is not a spreadsheet: it contains binary data ` + `rather than delimited text.`);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1870
1899
|
function looksLikeZip(bytes) {
|
|
1871
1900
|
return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
|
|
1872
1901
|
}
|
|
@@ -1889,4 +1918,4 @@ function withCsvFileName(source, options) {
|
|
|
1889
1918
|
};
|
|
1890
1919
|
}
|
|
1891
1920
|
|
|
1892
|
-
//# debugId=
|
|
1921
|
+
//# debugId=361575F73A84A17764756E2164756E21
|