reamkit 1.13.0 → 1.15.0
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 +34 -8
- package/dist/esm/core/converter/facade.d.ts +8 -1
- package/dist/esm/core/converter/facade.js +34 -8
- package/dist/esm/core/converter/ream.d.ts +1 -0
- package/dist/esm/core/converter/ream.js +13 -8
- package/dist/esm/core/document-model/types.d.ts +2 -0
- package/dist/esm/core/ir/features.d.ts +1 -0
- package/dist/esm/core/ir/features.js +1 -0
- package/dist/esm/core/ir/sheet.d.ts +42 -3
- package/dist/esm/core/ole/cfb.d.ts +19 -0
- package/dist/esm/core/ole/cfb.js +158 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +81 -1
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +188 -0
- package/dist/esm/excel/comments-parser.d.ts +4 -0
- package/dist/esm/excel/comments-parser.js +104 -0
- package/dist/esm/excel/conditional-format.d.ts +5 -3
- package/dist/esm/excel/conditional-format.js +248 -12
- package/dist/esm/excel/form-control-parser.d.ts +6 -0
- package/dist/esm/excel/form-control-parser.js +29 -0
- package/dist/esm/excel/formula/context.d.ts +11 -0
- package/dist/esm/excel/formula/dates.d.ts +11 -0
- package/dist/esm/excel/formula/dates.js +46 -0
- package/dist/esm/excel/formula/eval.d.ts +11 -0
- package/dist/esm/excel/formula/eval.js +196 -0
- package/dist/esm/excel/formula/functions.d.ts +7 -0
- package/dist/esm/excel/formula/functions.js +1359 -0
- package/dist/esm/excel/formula/index.d.ts +14 -0
- package/dist/esm/excel/formula/index.js +17 -0
- package/dist/esm/excel/formula/lexer.d.ts +8 -0
- package/dist/esm/excel/formula/lexer.js +163 -0
- package/dist/esm/excel/formula/parser.d.ts +57 -0
- package/dist/esm/excel/formula/parser.js +274 -0
- package/dist/esm/excel/formula/value.d.ts +52 -0
- package/dist/esm/excel/formula/value.js +102 -0
- package/dist/esm/excel/header-footer.d.ts +2 -0
- package/dist/esm/excel/header-footer.js +127 -0
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/number-format.d.ts +2 -0
- package/dist/esm/excel/number-format.js +5 -1
- package/dist/esm/excel/print-model.d.ts +7 -2
- package/dist/esm/excel/print-model.js +123 -16
- package/dist/esm/excel/shared-strings-parser.d.ts +6 -1
- package/dist/esm/excel/shared-strings-parser.js +91 -12
- package/dist/esm/excel/sheet-drawing.d.ts +13 -1
- package/dist/esm/excel/sheet-drawing.js +47 -14
- package/dist/esm/excel/sheet-shape-parser.d.ts +4 -0
- package/dist/esm/excel/sheet-shape-parser.js +96 -0
- package/dist/esm/excel/sheet-to-flow.d.ts +4 -1
- package/dist/esm/excel/sheet-to-flow.js +170 -3
- package/dist/esm/excel/styles-parser.js +37 -0
- package/dist/esm/excel/worksheet-parser.js +191 -1
- package/dist/esm/excel/xls/biff-chart.d.ts +8 -0
- package/dist/esm/excel/xls/biff-chart.js +121 -0
- package/dist/esm/excel/xls/biff-reader.d.ts +4 -0
- package/dist/esm/excel/xls/biff-reader.js +1418 -0
- package/dist/esm/excel/xls/biff-styles.d.ts +8 -0
- package/dist/esm/excel/xls/biff-styles.js +299 -0
- package/dist/esm/excel/xls/escher.d.ts +24 -0
- package/dist/esm/excel/xls/escher.js +171 -0
- package/dist/esm/excel/xls/xls-reader.d.ts +3 -0
- package/dist/esm/excel/xls/xls-reader.js +30 -0
- package/dist/esm/excel/xlsx-reader.d.ts +2 -1
- package/dist/esm/excel/xlsx-reader.js +134 -16
- package/dist/esm/excel/xlsx-to-pdf.d.ts +1 -0
- package/dist/esm/excel/xlsx-to-pdf.js +2 -2
- package/dist/esm/excel/xlsx-writer.js +12 -2
- package/dist/esm/layout/styled-layout.js +29 -0
- package/dist/esm/pptx/ppt/ppt-reader.d.ts +4 -0
- package/dist/esm/pptx/ppt/ppt-reader.js +337 -0
- package/dist/esm/pptx/ppt/ppt-text.d.ts +69 -0
- package/dist/esm/pptx/ppt/ppt-text.js +756 -0
- package/dist/esm/pptx/slide-parser.d.ts +4 -2
- package/dist/esm/pptx/slide-parser.js +1 -1
- package/dist/esm/word/doc/doc-reader.d.ts +4 -0
- package/dist/esm/word/doc/doc-reader.js +421 -0
- package/dist/esm/word/doc/doc-text.d.ts +79 -0
- package/dist/esm/word/doc/doc-text.js +706 -0
- package/package.json +1 -1
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { serialFromDate, timePeriodMatches } from "./formula/dates.js";
|
|
2
|
+
import { BLANK, bool, err, num, str } from "./formula/value.js";
|
|
3
|
+
import { NO_SHIFT, evaluate } from "./formula/eval.js";
|
|
4
|
+
import { compileFormula, evaluateToBool } from "./formula/index.js";
|
|
1
5
|
//#region src/excel/conditional-format.ts
|
|
2
|
-
function buildConditionalFormatter(conditionalFormats, styles, cells) {
|
|
6
|
+
function buildConditionalFormatter(conditionalFormats, styles, cells, resolveText, date1904 = false, now, sheetGrids, currentSheet, definedNames) {
|
|
3
7
|
if (!conditionalFormats || conditionalFormats.length === 0) return void 0;
|
|
4
8
|
const dxfs = styles.dxfs ?? [];
|
|
5
9
|
const flat = [];
|
|
@@ -18,41 +22,107 @@ function buildConditionalFormatter(conditionalFormats, styles, cells) {
|
|
|
18
22
|
rule,
|
|
19
23
|
iconThresholds: resolveIconThresholds(rule, cf.ranges, cells)
|
|
20
24
|
});
|
|
21
|
-
else flat.push({
|
|
25
|
+
else if (rule.type === "top10") flat.push({
|
|
26
|
+
ranges: cf.ranges,
|
|
27
|
+
rule,
|
|
28
|
+
threshold: resolveTop10(rule, cf.ranges, cells)
|
|
29
|
+
});
|
|
30
|
+
else if (rule.type === "aboveAverage") flat.push({
|
|
31
|
+
ranges: cf.ranges,
|
|
32
|
+
rule,
|
|
33
|
+
threshold: resolveAboveAverage(rule, cf.ranges, cells)
|
|
34
|
+
});
|
|
35
|
+
else if (rule.type === "duplicateValues" || rule.type === "uniqueValues") flat.push({
|
|
36
|
+
ranges: cf.ranges,
|
|
37
|
+
rule,
|
|
38
|
+
dupKeys: resolveDupUnique(rule, cf.ranges, cells, resolveText)
|
|
39
|
+
});
|
|
40
|
+
else if (rule.type === "expression") {
|
|
41
|
+
const first = cf.ranges[0];
|
|
42
|
+
flat.push({
|
|
43
|
+
ranges: cf.ranges,
|
|
44
|
+
rule,
|
|
45
|
+
compiled: compileFormula(rule.formula),
|
|
46
|
+
...first ? { origin: {
|
|
47
|
+
row: first.startRow,
|
|
48
|
+
col: first.startColumn
|
|
49
|
+
} } : {}
|
|
50
|
+
});
|
|
51
|
+
} else flat.push({
|
|
22
52
|
ranges: cf.ranges,
|
|
23
53
|
rule
|
|
24
54
|
});
|
|
25
55
|
flat.sort((a, b) => a.rule.priority - b.rule.priority);
|
|
26
|
-
|
|
27
|
-
|
|
56
|
+
const hasExpr = flat.some((f) => f.rule.type === "expression");
|
|
57
|
+
const nowSerial = now !== void 0 ? serialFromDate(now, date1904) : void 0;
|
|
58
|
+
const cross = hasExpr && sheetGrids ? {
|
|
59
|
+
sheets: [...sheetGrids].map(([name, ws]) => ({
|
|
60
|
+
name,
|
|
61
|
+
cells: ws.cells
|
|
62
|
+
})),
|
|
63
|
+
...currentSheet !== void 0 ? { currentSheet } : {},
|
|
64
|
+
definedNames: definedNames ?? []
|
|
65
|
+
} : void 0;
|
|
66
|
+
const ctx = hasExpr ? buildEvalContext(cells, resolveText, nowSerial, date1904, cross) : void 0;
|
|
67
|
+
return (row, col, value, text) => {
|
|
68
|
+
if (!hasExpr && value === void 0 && (text === void 0 || text.length === 0)) return;
|
|
69
|
+
const dupKey = dupKeyOf(value, text);
|
|
28
70
|
let textFmt;
|
|
29
71
|
let textClaimed = false;
|
|
30
72
|
let bar;
|
|
31
73
|
let icon;
|
|
32
|
-
|
|
74
|
+
const claim = (dxfId) => {
|
|
75
|
+
textFmt = dxfToOverride(dxfs[dxfId]);
|
|
76
|
+
textClaimed = true;
|
|
77
|
+
};
|
|
78
|
+
for (const { ranges, rule, scale, bar: resolved, iconThresholds, threshold, dupKeys, compiled, origin } of flat) {
|
|
33
79
|
if (!coversCell(ranges, row, col)) continue;
|
|
34
80
|
switch (rule.type) {
|
|
35
81
|
case "cellIs":
|
|
36
|
-
if (!textClaimed && cellIsMatches(rule.operator, value, rule.formulas))
|
|
37
|
-
textFmt = dxfToOverride(dxfs[rule.dxfId]);
|
|
38
|
-
textClaimed = true;
|
|
39
|
-
}
|
|
82
|
+
if (!textClaimed && value !== void 0 && cellIsMatches(rule.operator, value, rule.formulas)) claim(rule.dxfId);
|
|
40
83
|
break;
|
|
41
84
|
case "colorScale":
|
|
42
|
-
if (!textClaimed && scale) {
|
|
85
|
+
if (!textClaimed && value !== void 0 && scale) {
|
|
43
86
|
textFmt = { fillHex: rgbHex(colorScaleColor(scale, value)) };
|
|
44
87
|
textClaimed = true;
|
|
45
88
|
}
|
|
46
89
|
break;
|
|
47
90
|
case "dataBar":
|
|
48
|
-
if (!bar && resolved) bar = dataBarBar(resolved, value);
|
|
91
|
+
if (!bar && value !== void 0 && resolved) bar = dataBarBar(resolved, value);
|
|
49
92
|
break;
|
|
50
93
|
case "iconSet":
|
|
51
|
-
if (!icon && iconThresholds) {
|
|
94
|
+
if (!icon && value !== void 0 && iconThresholds) {
|
|
52
95
|
const bucket = iconBucket(iconThresholds, value);
|
|
53
96
|
icon = iconToCell(rule.iconSet, iconThresholds.length, bucket, rule.reverse ?? false);
|
|
54
97
|
}
|
|
55
98
|
break;
|
|
99
|
+
case "top10":
|
|
100
|
+
case "aboveAverage":
|
|
101
|
+
if (!textClaimed && value !== void 0 && threshold && thresholdMatches(threshold, value)) claim(rule.dxfId);
|
|
102
|
+
break;
|
|
103
|
+
case "duplicateValues":
|
|
104
|
+
case "uniqueValues":
|
|
105
|
+
if (!textClaimed && dupKey !== void 0 && dupKeys?.has(dupKey)) claim(rule.dxfId);
|
|
106
|
+
break;
|
|
107
|
+
case "containsText":
|
|
108
|
+
case "notContainsText":
|
|
109
|
+
case "beginsWith":
|
|
110
|
+
case "endsWith":
|
|
111
|
+
if (!textClaimed && text !== void 0 && textRuleMatches(rule, text)) claim(rule.dxfId);
|
|
112
|
+
break;
|
|
113
|
+
case "expression":
|
|
114
|
+
if (!textClaimed && compiled && ctx && origin) {
|
|
115
|
+
if (evaluateToBool(compiled, ctx, {
|
|
116
|
+
dRow: row - origin.row,
|
|
117
|
+
dCol: col - origin.col,
|
|
118
|
+
curRow: row,
|
|
119
|
+
curCol: col
|
|
120
|
+
})) claim(rule.dxfId);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
case "timePeriod":
|
|
124
|
+
if (!textClaimed && value !== void 0 && nowSerial !== void 0 && timePeriodMatches(rule.timePeriod, value, nowSerial, date1904)) claim(rule.dxfId);
|
|
125
|
+
break;
|
|
56
126
|
}
|
|
57
127
|
}
|
|
58
128
|
if (!bar && !icon) return textFmt;
|
|
@@ -63,6 +133,121 @@ function buildConditionalFormatter(conditionalFormats, styles, cells) {
|
|
|
63
133
|
};
|
|
64
134
|
};
|
|
65
135
|
}
|
|
136
|
+
var COLS = 16384;
|
|
137
|
+
function indexCells(cells, resolveText) {
|
|
138
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
139
|
+
const populated = [];
|
|
140
|
+
for (const cell of cells) {
|
|
141
|
+
const s = cellToScalar(cell, resolveText);
|
|
142
|
+
if (s.t === "blank") continue;
|
|
143
|
+
byKey.set(cell.row * COLS + cell.column, s);
|
|
144
|
+
populated.push({
|
|
145
|
+
row: cell.row,
|
|
146
|
+
col: cell.column,
|
|
147
|
+
value: s
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
byKey,
|
|
152
|
+
populated
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function eachInMap(map, rect, visit) {
|
|
156
|
+
for (const c of map.populated) if (c.row >= rect.r0 && c.row <= rect.r1 && c.col >= rect.c0 && c.col <= rect.c1) visit(c.row, c.col, c.value);
|
|
157
|
+
}
|
|
158
|
+
function buildEvalContext(cells, resolveText, nowSerial, date1904, cross) {
|
|
159
|
+
const self = indexCells(cells, resolveText);
|
|
160
|
+
const base = {
|
|
161
|
+
nowSerial,
|
|
162
|
+
date1904,
|
|
163
|
+
getCell: (row, col) => self.byKey.get(row * COLS + col) ?? BLANK,
|
|
164
|
+
eachCell: (rect, visit) => eachInMap(self, rect, visit)
|
|
165
|
+
};
|
|
166
|
+
if (!cross) return base;
|
|
167
|
+
const sheetByName = /* @__PURE__ */ new Map();
|
|
168
|
+
cross.sheets.forEach((s, i) => sheetByName.set(s.name.toLowerCase(), i));
|
|
169
|
+
const maps = new Array(cross.sheets.length);
|
|
170
|
+
const sheetData = (idx) => maps[idx] ??= indexCells(cross.sheets[idx].cells, resolveText);
|
|
171
|
+
const currentIdx = cross.currentSheet !== void 0 ? sheetByName.get(cross.currentSheet.toLowerCase()) : void 0;
|
|
172
|
+
const nameMap = /* @__PURE__ */ new Map();
|
|
173
|
+
for (const dn of cross.definedNames) {
|
|
174
|
+
if (dn.localSheetId !== void 0 && dn.localSheetId !== currentIdx) continue;
|
|
175
|
+
const key = dn.name.toLowerCase();
|
|
176
|
+
if (dn.localSheetId !== void 0 || !nameMap.has(key)) nameMap.set(key, dn);
|
|
177
|
+
}
|
|
178
|
+
const resolving = /* @__PURE__ */ new Set();
|
|
179
|
+
const ctx = {
|
|
180
|
+
...base,
|
|
181
|
+
sheetIndex: (name) => sheetByName.get(name.toLowerCase()),
|
|
182
|
+
getCellOn: (sheet, row, col) => sheetData(sheet).byKey.get(row * COLS + col) ?? BLANK,
|
|
183
|
+
eachCellOn: (sheet, rect, visit) => eachInMap(sheetData(sheet), rect, visit),
|
|
184
|
+
resolveName: (rawName) => {
|
|
185
|
+
const key = rawName.toLowerCase();
|
|
186
|
+
const dn = nameMap.get(key);
|
|
187
|
+
if (!dn || resolving.has(key)) return void 0;
|
|
188
|
+
const compiled = compileFormula(dn.value.startsWith("=") ? dn.value.slice(1) : dn.value);
|
|
189
|
+
if (!compiled) return void 0;
|
|
190
|
+
const ast = compiled.ast;
|
|
191
|
+
if (ast.k === "num") return num(ast.v);
|
|
192
|
+
if (ast.k === "str") return str(ast.v);
|
|
193
|
+
if (ast.k === "bool") return bool(ast.v);
|
|
194
|
+
if (ast.k !== "cell" && ast.k !== "range") return void 0;
|
|
195
|
+
resolving.add(key);
|
|
196
|
+
try {
|
|
197
|
+
return evaluate(ast, ctx, NO_SHIFT);
|
|
198
|
+
} finally {
|
|
199
|
+
resolving.delete(key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
return ctx;
|
|
204
|
+
}
|
|
205
|
+
function cellToScalar(cell, resolveText) {
|
|
206
|
+
switch (cell.type) {
|
|
207
|
+
case "n": {
|
|
208
|
+
const n = Number(cell.rawValue);
|
|
209
|
+
return Number.isFinite(n) ? num(n) : BLANK;
|
|
210
|
+
}
|
|
211
|
+
case "b": return bool(cell.rawValue === "1" || cell.rawValue.toUpperCase() === "TRUE");
|
|
212
|
+
case "e": return err(toFErr(cell.rawValue));
|
|
213
|
+
case "s":
|
|
214
|
+
case "str":
|
|
215
|
+
case "inlineStr": return str(resolveText ? resolveText(cell) : cell.inlineText ?? cell.rawValue);
|
|
216
|
+
default: return cell.rawValue.length > 0 ? str(cell.rawValue) : BLANK;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
var FERRS = new Set([
|
|
220
|
+
"#NULL!",
|
|
221
|
+
"#DIV/0!",
|
|
222
|
+
"#VALUE!",
|
|
223
|
+
"#REF!",
|
|
224
|
+
"#NAME?",
|
|
225
|
+
"#NUM!",
|
|
226
|
+
"#N/A"
|
|
227
|
+
]);
|
|
228
|
+
function toFErr(raw) {
|
|
229
|
+
return FERRS.has(raw) ? raw : "#VALUE!";
|
|
230
|
+
}
|
|
231
|
+
function dupKeyOf(value, text) {
|
|
232
|
+
if (value !== void 0) return `n:${value}`;
|
|
233
|
+
if (text === void 0) return void 0;
|
|
234
|
+
const k = text.trim().toLowerCase();
|
|
235
|
+
return k.length > 0 ? `s:${k}` : void 0;
|
|
236
|
+
}
|
|
237
|
+
function textRuleMatches(rule, text) {
|
|
238
|
+
const hay = text.toLowerCase();
|
|
239
|
+
const needle = rule.text.toLowerCase();
|
|
240
|
+
switch (rule.type) {
|
|
241
|
+
case "containsText": return hay.includes(needle);
|
|
242
|
+
case "notContainsText": return !hay.includes(needle);
|
|
243
|
+
case "beginsWith": return hay.startsWith(needle);
|
|
244
|
+
case "endsWith": return hay.endsWith(needle);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function thresholdMatches(t, value) {
|
|
248
|
+
if (t.below) return t.orEqual ? value <= t.threshold : value < t.threshold;
|
|
249
|
+
return t.orEqual ? value >= t.threshold : value > t.threshold;
|
|
250
|
+
}
|
|
66
251
|
function coversCell(ranges, row, col) {
|
|
67
252
|
for (const r of ranges) if (row >= r.startRow && row <= r.endRow && col >= r.startColumn && col <= r.endColumn) return true;
|
|
68
253
|
return false;
|
|
@@ -323,5 +508,56 @@ function iconColor(count, e) {
|
|
|
323
508
|
const ramp = ICON_RAMPS[count] ?? ICON_RAMPS[3];
|
|
324
509
|
return ramp[Math.max(0, Math.min(ramp.length - 1, e))];
|
|
325
510
|
}
|
|
511
|
+
function resolveTop10(rule, ranges, cells) {
|
|
512
|
+
const vals = collectRangeValues(cells, ranges);
|
|
513
|
+
if (vals.length === 0) return void 0;
|
|
514
|
+
const n = vals.length;
|
|
515
|
+
const raw = rule.percent ? Math.floor(n * rule.rank / 100) : rule.rank;
|
|
516
|
+
const count = Math.max(1, Math.min(n, raw));
|
|
517
|
+
return {
|
|
518
|
+
threshold: [...vals].sort((a, b) => rule.bottom ? a - b : b - a)[count - 1],
|
|
519
|
+
below: rule.bottom,
|
|
520
|
+
orEqual: true
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function resolveAboveAverage(rule, ranges, cells) {
|
|
524
|
+
const vals = collectRangeValues(cells, ranges);
|
|
525
|
+
if (vals.length === 0) return void 0;
|
|
526
|
+
const mean = vals.reduce((s, v) => s + v, 0) / vals.length;
|
|
527
|
+
let threshold = mean;
|
|
528
|
+
if (rule.stdDev !== void 0 && rule.stdDev > 0) {
|
|
529
|
+
const variance = vals.reduce((s, v) => s + (v - mean) * (v - mean), 0) / vals.length;
|
|
530
|
+
const sd = Math.sqrt(variance);
|
|
531
|
+
threshold = rule.aboveAverage ? mean + rule.stdDev * sd : mean - rule.stdDev * sd;
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
threshold,
|
|
535
|
+
below: !rule.aboveAverage,
|
|
536
|
+
orEqual: rule.equalAverage
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function resolveDupUnique(rule, ranges, cells, resolveText) {
|
|
540
|
+
const counts = /* @__PURE__ */ new Map();
|
|
541
|
+
for (const cell of cells) {
|
|
542
|
+
if (!coversCell(ranges, cell.row, cell.column)) continue;
|
|
543
|
+
const key = cellDupKey(cell, resolveText);
|
|
544
|
+
if (key === void 0) continue;
|
|
545
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
546
|
+
}
|
|
547
|
+
if (counts.size === 0) return void 0;
|
|
548
|
+
const qualifies = rule.type === "duplicateValues" ? (c) => c >= 2 : (c) => c === 1;
|
|
549
|
+
const out = /* @__PURE__ */ new Set();
|
|
550
|
+
for (const [k, c] of counts) if (qualifies(c)) out.add(k);
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
function cellDupKey(cell, resolveText) {
|
|
554
|
+
if (cell.type === "n") {
|
|
555
|
+
const n = Number(cell.rawValue);
|
|
556
|
+
return Number.isFinite(n) ? `n:${n}` : void 0;
|
|
557
|
+
}
|
|
558
|
+
if (!resolveText) return void 0;
|
|
559
|
+
const k = resolveText(cell).trim().toLowerCase();
|
|
560
|
+
return k.length > 0 ? `s:${k}` : void 0;
|
|
561
|
+
}
|
|
326
562
|
//#endregion
|
|
327
563
|
export { buildConditionalFormatter };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
//#region src/excel/form-control-parser.ts
|
|
3
|
+
var decoder = new TextDecoder("utf-8");
|
|
4
|
+
var parser = new XMLParser({
|
|
5
|
+
ignoreAttributes: false,
|
|
6
|
+
attributeNamePrefix: "@_",
|
|
7
|
+
parseAttributeValue: false,
|
|
8
|
+
removeNSPrefix: true
|
|
9
|
+
});
|
|
10
|
+
function parseFormControlProps(data) {
|
|
11
|
+
const pr = parser.parse(decoder.decode(data))["formControlPr"];
|
|
12
|
+
const obj = pr && typeof pr === "object" ? pr : void 0;
|
|
13
|
+
if (!obj) return {};
|
|
14
|
+
const objectType = strAttr(obj, "objectType");
|
|
15
|
+
const checkedRaw = strAttr(obj, "checked");
|
|
16
|
+
const valRaw = strAttr(obj, "val");
|
|
17
|
+
const value = valRaw !== void 0 && /^-?\d+$/.test(valRaw) ? Number(valRaw) : void 0;
|
|
18
|
+
return {
|
|
19
|
+
...objectType ? { objectType } : {},
|
|
20
|
+
...checkedRaw !== void 0 ? { checked: checkedRaw === "Checked" || checkedRaw === "1" } : {},
|
|
21
|
+
...value !== void 0 ? { value } : {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function strAttr(obj, key) {
|
|
25
|
+
const v = obj[`@_${key}`];
|
|
26
|
+
return typeof v === "string" ? v : void 0;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { parseFormControlProps };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FValue, Rect, Scalar } from './value.js';
|
|
2
|
+
export interface EvalContext {
|
|
3
|
+
readonly getCell: (row: number, col: number) => Scalar;
|
|
4
|
+
readonly eachCell: (rect: Rect, visit: (row: number, col: number, value: Scalar) => void) => void;
|
|
5
|
+
readonly nowSerial: number | undefined;
|
|
6
|
+
readonly date1904: boolean;
|
|
7
|
+
readonly sheetIndex?: (name: string) => number | undefined;
|
|
8
|
+
readonly getCellOn?: (sheet: number, row: number, col: number) => Scalar;
|
|
9
|
+
readonly eachCellOn?: (sheet: number, rect: Rect, visit: (row: number, col: number, value: Scalar) => void) => void;
|
|
10
|
+
readonly resolveName?: (name: string) => FValue | undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TimePeriodKind } from '../../core/spreadsheet-model/index.js';
|
|
2
|
+
export interface DateParts {
|
|
3
|
+
readonly year: number;
|
|
4
|
+
readonly month: number;
|
|
5
|
+
readonly day: number;
|
|
6
|
+
readonly dow: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function serialToParts(serial: number, date1904: boolean): DateParts;
|
|
9
|
+
export declare function serialFromDate(d: Date, date1904: boolean): number;
|
|
10
|
+
export declare function serialFromYmd(year: number, month1: number, day: number, date1904: boolean): number;
|
|
11
|
+
export declare function timePeriodMatches(period: TimePeriodKind, cellSerial: number, nowSerial: number, date1904: boolean): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { excelSerialFromUtcParts, excelSerialToDate } from "../number-format.js";
|
|
2
|
+
//#region src/excel/formula/dates.ts
|
|
3
|
+
function serialToParts(serial, date1904) {
|
|
4
|
+
const d = excelSerialToDate(Math.floor(serial), date1904);
|
|
5
|
+
return {
|
|
6
|
+
year: d.getUTCFullYear(),
|
|
7
|
+
month: d.getUTCMonth() + 1,
|
|
8
|
+
day: d.getUTCDate(),
|
|
9
|
+
dow: d.getUTCDay()
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function serialFromDate(d, date1904) {
|
|
13
|
+
return excelSerialFromUtcParts(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), date1904);
|
|
14
|
+
}
|
|
15
|
+
function serialFromYmd(year, month1, day, date1904) {
|
|
16
|
+
return excelSerialFromUtcParts(year, month1 - 1, day, date1904);
|
|
17
|
+
}
|
|
18
|
+
function timePeriodMatches(period, cellSerial, nowSerial, date1904) {
|
|
19
|
+
const cell = Math.floor(cellSerial);
|
|
20
|
+
const today = Math.floor(nowSerial);
|
|
21
|
+
switch (period) {
|
|
22
|
+
case "today": return cell === today;
|
|
23
|
+
case "yesterday": return cell === today - 1;
|
|
24
|
+
case "tomorrow": return cell === today + 1;
|
|
25
|
+
case "last7Days": return today - cell >= 0 && today - cell <= 6;
|
|
26
|
+
case "thisWeek":
|
|
27
|
+
case "lastWeek":
|
|
28
|
+
case "nextWeek": {
|
|
29
|
+
const lo = today - serialToParts(today, date1904).dow + (period === "lastWeek" ? -7 : period === "nextWeek" ? 7 : 0);
|
|
30
|
+
return cell >= lo && cell <= lo + 6;
|
|
31
|
+
}
|
|
32
|
+
case "thisMonth":
|
|
33
|
+
case "lastMonth":
|
|
34
|
+
case "nextMonth": {
|
|
35
|
+
const t = serialToParts(today, date1904);
|
|
36
|
+
const delta = period === "lastMonth" ? -1 : period === "nextMonth" ? 1 : 0;
|
|
37
|
+
const idx = t.year * 12 + (t.month - 1) + delta;
|
|
38
|
+
const targetYear = Math.floor(idx / 12);
|
|
39
|
+
const targetMonth = idx % 12 + 1;
|
|
40
|
+
const c = serialToParts(cell, date1904);
|
|
41
|
+
return c.year === targetYear && c.month === targetMonth;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { serialFromDate, serialFromYmd, serialToParts, timePeriodMatches };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Ast } from './parser.js';
|
|
2
|
+
import { EvalContext } from './context.js';
|
|
3
|
+
import { FValue } from './value.js';
|
|
4
|
+
export interface Shift {
|
|
5
|
+
readonly dRow: number;
|
|
6
|
+
readonly dCol: number;
|
|
7
|
+
readonly curRow?: number;
|
|
8
|
+
readonly curCol?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const NO_SHIFT: Shift;
|
|
11
|
+
export declare function evaluate(ast: Ast, ctx: EvalContext, shift: Shift): FValue;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { bool, deref, err, num, str, toNumber, toText } from "./value.js";
|
|
2
|
+
import { callFn } from "./functions.js";
|
|
3
|
+
//#region src/excel/formula/eval.ts
|
|
4
|
+
var NO_SHIFT = {
|
|
5
|
+
dRow: 0,
|
|
6
|
+
dCol: 0
|
|
7
|
+
};
|
|
8
|
+
function evaluate(ast, ctx, shift) {
|
|
9
|
+
switch (ast.k) {
|
|
10
|
+
case "num": return num(ast.v);
|
|
11
|
+
case "str": return str(ast.v);
|
|
12
|
+
case "bool": return bool(ast.v);
|
|
13
|
+
case "err": return err(ast.v);
|
|
14
|
+
case "cell": {
|
|
15
|
+
const sheet = sheetIdx(ast.sheet, ctx);
|
|
16
|
+
if (sheet === false) return err("#REF!");
|
|
17
|
+
const r = resolveRef(ast.ref, shift);
|
|
18
|
+
if (!r) return err("#REF!");
|
|
19
|
+
return {
|
|
20
|
+
t: "ref",
|
|
21
|
+
rect: {
|
|
22
|
+
r0: r.row,
|
|
23
|
+
c0: r.col,
|
|
24
|
+
r1: r.row,
|
|
25
|
+
c1: r.col
|
|
26
|
+
},
|
|
27
|
+
...sheet !== void 0 ? { sheet } : {}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
case "range": {
|
|
31
|
+
const sheet = sheetIdx(ast.sheet, ctx);
|
|
32
|
+
if (sheet === false) return err("#REF!");
|
|
33
|
+
const a = resolveRef(ast.a, shift);
|
|
34
|
+
const b = resolveRef(ast.b, shift);
|
|
35
|
+
if (!a || !b) return err("#REF!");
|
|
36
|
+
return {
|
|
37
|
+
t: "ref",
|
|
38
|
+
rect: {
|
|
39
|
+
r0: Math.min(a.row, b.row),
|
|
40
|
+
c0: Math.min(a.col, b.col),
|
|
41
|
+
r1: Math.max(a.row, b.row),
|
|
42
|
+
c1: Math.max(a.col, b.col)
|
|
43
|
+
},
|
|
44
|
+
...sheet !== void 0 ? { sheet } : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
case "name": return ctx.resolveName?.(ast.name) ?? err("#NAME?");
|
|
48
|
+
case "array": return {
|
|
49
|
+
t: "arr",
|
|
50
|
+
rows: ast.rows.map((row) => row.map((el) => deref(evaluate(el, ctx, shift), ctx)))
|
|
51
|
+
};
|
|
52
|
+
case "unary": return evalUnary(ast.op, evaluate(ast.x, ctx, shift), ctx);
|
|
53
|
+
case "pct": {
|
|
54
|
+
const n = toNumber(evaluate(ast.x, ctx, shift), ctx);
|
|
55
|
+
return typeof n === "string" ? err(n) : num(n / 100);
|
|
56
|
+
}
|
|
57
|
+
case "bin": return evalBin(ast.op, ast.a, ast.b, ctx, shift);
|
|
58
|
+
case "call": return callFn(ast.name, ast.args, (a) => evaluate(a, ctx, shift), ctx, shift);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function resolveRef(ref, shift) {
|
|
62
|
+
const row = ref.row.abs ? ref.row.index : ref.row.index + shift.dRow;
|
|
63
|
+
const col = ref.col.abs ? ref.col.index : ref.col.index + shift.dCol;
|
|
64
|
+
if (row < 0 || col < 0) return void 0;
|
|
65
|
+
return {
|
|
66
|
+
row,
|
|
67
|
+
col
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function sheetIdx(name, ctx) {
|
|
71
|
+
if (name === void 0) return void 0;
|
|
72
|
+
const idx = ctx.sheetIndex?.(name);
|
|
73
|
+
return idx === void 0 ? false : idx;
|
|
74
|
+
}
|
|
75
|
+
function evalUnary(op, x, ctx) {
|
|
76
|
+
const n = toNumber(x, ctx);
|
|
77
|
+
if (typeof n === "string") return err(n);
|
|
78
|
+
return num(op === "-" ? -n : n);
|
|
79
|
+
}
|
|
80
|
+
function evalBin(op, aAst, bAst, ctx, shift) {
|
|
81
|
+
const a = evaluate(aAst, ctx, shift);
|
|
82
|
+
const b = evaluate(bAst, ctx, shift);
|
|
83
|
+
if (a.t === "arr" || b.t === "arr") return broadcastBin(op, a, b, ctx);
|
|
84
|
+
return applyBin(op, a, b, ctx);
|
|
85
|
+
}
|
|
86
|
+
function applyBin(op, a, b, ctx) {
|
|
87
|
+
if (op === "&") {
|
|
88
|
+
const ta = toText(a, ctx);
|
|
89
|
+
if (typeof ta !== "string") return err(ta);
|
|
90
|
+
const tb = toText(b, ctx);
|
|
91
|
+
if (typeof tb !== "string") return err(tb);
|
|
92
|
+
return str(ta + tb);
|
|
93
|
+
}
|
|
94
|
+
if (op === "=" || op === "<>" || op === "<" || op === ">" || op === "<=" || op === ">=") return compareOp(op, deref(a, ctx), deref(b, ctx));
|
|
95
|
+
const na = toNumber(a, ctx);
|
|
96
|
+
if (typeof na === "string") return err(na);
|
|
97
|
+
const nb = toNumber(b, ctx);
|
|
98
|
+
if (typeof nb === "string") return err(nb);
|
|
99
|
+
switch (op) {
|
|
100
|
+
case "+": return num(na + nb);
|
|
101
|
+
case "-": return num(na - nb);
|
|
102
|
+
case "*": return num(na * nb);
|
|
103
|
+
case "/": return nb === 0 ? err("#DIV/0!") : num(na / nb);
|
|
104
|
+
case "^": {
|
|
105
|
+
const r = Math.pow(na, nb);
|
|
106
|
+
return Number.isNaN(r) ? err("#NUM!") : num(r);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function broadcastBin(op, a, b, ctx) {
|
|
111
|
+
const ar = a.t === "arr" ? a.rows : [[deref(a, ctx)]];
|
|
112
|
+
const br = b.t === "arr" ? b.rows : [[deref(b, ctx)]];
|
|
113
|
+
const nRows = Math.max(ar.length, br.length);
|
|
114
|
+
const out = [];
|
|
115
|
+
for (let r = 0; r < nRows; r++) {
|
|
116
|
+
const arow = ar[ar.length === 1 ? 0 : r];
|
|
117
|
+
const brow = br[br.length === 1 ? 0 : r];
|
|
118
|
+
if (!arow || !brow) return err("#VALUE!");
|
|
119
|
+
const nCols = Math.max(arow.length, brow.length);
|
|
120
|
+
const row = [];
|
|
121
|
+
for (let c = 0; c < nCols; c++) {
|
|
122
|
+
const ea = arow[arow.length === 1 ? 0 : c];
|
|
123
|
+
const eb = brow[brow.length === 1 ? 0 : c];
|
|
124
|
+
if (ea === void 0 || eb === void 0) return err("#VALUE!");
|
|
125
|
+
const res = applyBin(op, ea, eb, ctx);
|
|
126
|
+
row.push(res.t === "ref" || res.t === "arr" ? err("#VALUE!") : res);
|
|
127
|
+
}
|
|
128
|
+
out.push(row);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
t: "arr",
|
|
132
|
+
rows: out
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function compareOp(op, a, b) {
|
|
136
|
+
const c = compare(a, b);
|
|
137
|
+
if (typeof c === "string") return err(c);
|
|
138
|
+
switch (op) {
|
|
139
|
+
case "=": return bool(c === 0);
|
|
140
|
+
case "<>": return bool(c !== 0);
|
|
141
|
+
case "<": return bool(c < 0);
|
|
142
|
+
case ">": return bool(c > 0);
|
|
143
|
+
case "<=": return bool(c <= 0);
|
|
144
|
+
case ">=": return bool(c >= 0);
|
|
145
|
+
default: return err("#VALUE!");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function compare(a, b) {
|
|
149
|
+
if (a.t === "err") return a.v;
|
|
150
|
+
if (b.t === "err") return b.v;
|
|
151
|
+
const na = normalizeBlank(a, b);
|
|
152
|
+
const nb = normalizeBlank(b, a);
|
|
153
|
+
const ca = category(na);
|
|
154
|
+
const cb = category(nb);
|
|
155
|
+
if (ca !== cb) return ca < cb ? -1 : 1;
|
|
156
|
+
if (na.t === "str" && nb.t === "str") {
|
|
157
|
+
const la = na.v.toLowerCase();
|
|
158
|
+
const lb = nb.v.toLowerCase();
|
|
159
|
+
return la < lb ? -1 : la > lb ? 1 : 0;
|
|
160
|
+
}
|
|
161
|
+
const va = scalarNumber(na);
|
|
162
|
+
const vb = scalarNumber(nb);
|
|
163
|
+
return va < vb ? -1 : va > vb ? 1 : 0;
|
|
164
|
+
}
|
|
165
|
+
function normalizeBlank(s, other) {
|
|
166
|
+
if (s.t !== "blank") return s;
|
|
167
|
+
switch (other.t) {
|
|
168
|
+
case "str": return {
|
|
169
|
+
t: "str",
|
|
170
|
+
v: ""
|
|
171
|
+
};
|
|
172
|
+
case "bool": return {
|
|
173
|
+
t: "bool",
|
|
174
|
+
v: false
|
|
175
|
+
};
|
|
176
|
+
default: return {
|
|
177
|
+
t: "num",
|
|
178
|
+
v: 0
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function category(s) {
|
|
183
|
+
switch (s.t) {
|
|
184
|
+
case "num": return 0;
|
|
185
|
+
case "str": return 1;
|
|
186
|
+
case "bool": return 2;
|
|
187
|
+
default: return 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function scalarNumber(s) {
|
|
191
|
+
if (s.t === "num") return s.v;
|
|
192
|
+
if (s.t === "bool") return s.v ? 1 : 0;
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
export { NO_SHIFT, evaluate };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Ast } from './parser.js';
|
|
2
|
+
import { EvalContext } from './context.js';
|
|
3
|
+
import { Shift } from './eval.js';
|
|
4
|
+
import { FValue } from './value.js';
|
|
5
|
+
type Ev = (a: Ast) => FValue;
|
|
6
|
+
export declare function callFn(name: string, args: ReadonlyArray<Ast>, ev: Ev, ctx: EvalContext, shift: Shift): FValue;
|
|
7
|
+
export {};
|