reamkit 1.31.0 → 1.32.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 +10 -0
- package/dist/esm/core/bidi/index.d.ts +10 -0
- package/dist/esm/core/bidi/index.js +18 -2
- package/dist/esm/core/converter/facade.js +10 -5
- package/dist/esm/core/converter/project.js +1 -0
- package/dist/esm/core/converter/ream.js +17 -6
- package/dist/esm/core/converter/sheet-images.d.ts +17 -0
- package/dist/esm/core/converter/sheet-images.js +33 -0
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +112 -0
- package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
- package/dist/esm/core/drawingml/chart-geometry.js +198 -108
- package/dist/esm/core/drawingml/chart-parser.js +73 -2
- package/dist/esm/core/drawingml/colors.d.ts +6 -0
- package/dist/esm/core/drawingml/colors.js +23 -3
- package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
- package/dist/esm/core/drawingml/shape-render.js +4 -2
- package/dist/esm/core/font/measure.d.ts +9 -0
- package/dist/esm/core/font/measure.js +9 -1
- package/dist/esm/core/fonts/families.js +4 -2
- package/dist/esm/core/fonts/scripts.js +4 -2
- package/dist/esm/core/ir/flow.d.ts +6 -0
- package/dist/esm/core/ir/sheet.d.ts +43 -0
- package/dist/esm/core/number-format.js +16 -11
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
- package/dist/esm/core/style-cascade/resolver.js +4 -2
- package/dist/esm/excel/column-bands.d.ts +39 -6
- package/dist/esm/excel/column-bands.js +41 -17
- package/dist/esm/excel/comments-parser.js +19 -0
- package/dist/esm/excel/conditional-format.d.ts +7 -3
- package/dist/esm/excel/conditional-format.js +77 -39
- package/dist/esm/excel/formula/context.d.ts +18 -0
- package/dist/esm/excel/formula/eval.js +51 -0
- package/dist/esm/excel/formula/index.d.ts +2 -2
- package/dist/esm/excel/formula/lexer.d.ts +1 -1
- package/dist/esm/excel/formula/lexer.js +41 -0
- package/dist/esm/excel/formula/parser.d.ts +25 -0
- package/dist/esm/excel/formula/parser.js +95 -0
- package/dist/esm/excel/print-model.d.ts +38 -1
- package/dist/esm/excel/print-model.js +522 -80
- package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -1
- package/dist/esm/excel/sheet-drawing.d.ts +38 -2
- package/dist/esm/excel/sheet-drawing.js +47 -13
- package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
- package/dist/esm/excel/sheet-shape-parser.js +18 -6
- package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
- package/dist/esm/excel/sheet-to-flow.js +434 -47
- package/dist/esm/excel/styles-parser.d.ts +9 -0
- package/dist/esm/excel/styles-parser.js +80 -26
- package/dist/esm/excel/table-parser.js +5 -0
- package/dist/esm/excel/table-style-presets.d.ts +26 -0
- package/dist/esm/excel/table-style-presets.js +1555 -0
- package/dist/esm/excel/table-style.d.ts +13 -0
- package/dist/esm/excel/table-style.js +75 -0
- package/dist/esm/excel/tint.d.ts +10 -0
- package/dist/esm/excel/tint.js +75 -0
- package/dist/esm/excel/vml-drawing.d.ts +41 -3
- package/dist/esm/excel/vml-drawing.js +143 -10
- package/dist/esm/excel/worksheet-parser.js +89 -15
- package/dist/esm/excel/xls/biff-reader.js +10 -2
- package/dist/esm/excel/xlsx-reader.js +195 -14
- package/dist/esm/excel/xlsx-writer.js +8 -7
- package/dist/esm/html/html-writer.js +202 -20
- package/dist/esm/layout/fit-pages.d.ts +11 -0
- package/dist/esm/layout/fit-pages.js +86 -0
- package/dist/esm/layout/line-paint.d.ts +58 -0
- package/dist/esm/layout/line-paint.js +107 -0
- package/dist/esm/layout/page-doc.d.ts +51 -0
- package/dist/esm/layout/page-doc.js +141 -1
- package/dist/esm/layout/styled-layout.d.ts +7 -0
- package/dist/esm/layout/styled-layout.js +274 -51
- package/dist/esm/layout/turned-section.js +14 -2
- package/dist/esm/markdown/markdown-writer.js +17 -6
- package/dist/esm/pdf/styled-page-emitter.js +49 -71
- package/dist/esm/pdf/styled-page-renderer.js +1 -1
- package/dist/esm/pdf-reader/font.js +3 -3
- package/dist/esm/svg/svg-text.d.ts +58 -0
- package/dist/esm/svg/svg-text.js +322 -0
- package/dist/esm/svg/svg-writer.js +135 -71
- package/dist/esm/word/docx-writer.js +11 -2
- package/package.json +5 -1
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { twipsToPt } from "../core/ir/units.js";
|
|
2
|
-
//#region src/excel/column-bands.ts
|
|
3
|
-
/** §18.3.1.70 — the width of the row-number column, in twips. */
|
|
4
|
-
var HEADING_COL_TWIPS = 460;
|
|
1
|
+
import { pt, twipsToPt } from "../core/ir/units.js";
|
|
5
2
|
/** The A1-style letters of an absolute column index (0 → A, 26 → AA). */
|
|
6
3
|
function columnLetters(index) {
|
|
7
4
|
let n = index;
|
|
@@ -20,14 +17,17 @@ function columnLetters(index) {
|
|
|
20
17
|
*
|
|
21
18
|
* @param rows The band's rows.
|
|
22
19
|
* @param widths The band's column widths in twips.
|
|
23
|
-
* @param
|
|
20
|
+
* @param columns The absolute index of each of those columns — a hidden
|
|
21
|
+
* one between them is skipped, and so is its letter.
|
|
24
22
|
* @param rowNumbers The absolute 1-based row number of each row.
|
|
23
|
+
* @param band How the headings stand, at the print scale.
|
|
25
24
|
* @returns The rows and widths with the heading band added.
|
|
26
25
|
*/
|
|
27
|
-
function withHeadingBand(rows, widths,
|
|
26
|
+
function withHeadingBand(rows, widths, columns, rowNumbers, band = {}) {
|
|
27
|
+
const { lettersPt, colTwips = 460, fontPt, lineWidthPt = .5 } = band;
|
|
28
28
|
const border = {
|
|
29
29
|
style: "single",
|
|
30
|
-
width:
|
|
30
|
+
width: lineWidthPt,
|
|
31
31
|
colorHex: "808080"
|
|
32
32
|
};
|
|
33
33
|
const borders = {
|
|
@@ -47,15 +47,21 @@ function withHeadingBand(rows, widths, colStart, rowNumbers) {
|
|
|
47
47
|
properties: { alignment: "center" },
|
|
48
48
|
runs: [{
|
|
49
49
|
text,
|
|
50
|
-
properties: {}
|
|
50
|
+
properties: fontPt !== void 0 ? { fontSizePt: pt(fontPt) } : {}
|
|
51
51
|
}]
|
|
52
52
|
}
|
|
53
53
|
}]
|
|
54
54
|
});
|
|
55
55
|
const letters = [head("")];
|
|
56
|
-
for (let i = 0; i < widths.length; i++) letters.push(head(columnLetters(
|
|
56
|
+
for (let i = 0; i < widths.length; i++) letters.push(head(columnLetters(columns[i] ?? i)));
|
|
57
57
|
const out = [{
|
|
58
|
-
properties: {
|
|
58
|
+
properties: {
|
|
59
|
+
isHeader: true,
|
|
60
|
+
...lettersPt !== void 0 ? {
|
|
61
|
+
height: pt(lettersPt),
|
|
62
|
+
heightRule: "exact"
|
|
63
|
+
} : {}
|
|
64
|
+
},
|
|
59
65
|
cells: letters
|
|
60
66
|
}];
|
|
61
67
|
rows.forEach((row, i) => {
|
|
@@ -66,7 +72,7 @@ function withHeadingBand(rows, widths, colStart, rowNumbers) {
|
|
|
66
72
|
});
|
|
67
73
|
return {
|
|
68
74
|
rows: out,
|
|
69
|
-
widths: [
|
|
75
|
+
widths: [colTwips, ...widths]
|
|
70
76
|
};
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
@@ -122,16 +128,33 @@ function computeColumnBands(columnWidths, contentWidthTwips, colBreaks) {
|
|
|
122
128
|
* @param titleRowIndex The print-title row, or -1.
|
|
123
129
|
* @param drawingReachTwips How far right the sheet's drawings reach (0 if none).
|
|
124
130
|
* @param headings §18.3.1.70 — the printed row/column headings, when the sheet
|
|
125
|
-
* asks for them: the absolute index of
|
|
126
|
-
*
|
|
127
|
-
*
|
|
131
|
+
* asks for them: the absolute index of each column and the sheet row number
|
|
132
|
+
* of each emitted row. Each band gets ITS OWN letters, which is what both
|
|
133
|
+
* references print across the top of a continuation page.
|
|
134
|
+
* @param sheet §20.5 — where the grid's first column stands on the sheet and
|
|
135
|
+
* where its printed drawings lie, at the print scale: each band is framed
|
|
136
|
+
* for the drawings over it (TableOverlay) and keeps the rows they cover.
|
|
128
137
|
* @returns One table body element per band, in band order.
|
|
129
138
|
*/
|
|
130
|
-
function bandedTables(rows, columnWidths, bands, properties, titleRowIndex = -1, drawingReachTwips = 0, headings) {
|
|
139
|
+
function bandedTables(rows, columnWidths, bands, properties, titleRowIndex = -1, drawingReachTwips = 0, headings, sheet) {
|
|
131
140
|
return bands.flatMap((band, bandIndex) => {
|
|
132
141
|
const bandLeft = columnWidths.slice(0, band.start).reduce((sum, w) => sum + w, 0);
|
|
133
142
|
const bandTwips = columnWidths.slice(band.start, band.end + 1);
|
|
134
143
|
const grid = bandTwips.map((w) => twipsToPt(w));
|
|
144
|
+
const sheetLeftPt = sheet ? sheet.sheetLeftPt + twipsToPt(bandLeft) : 0;
|
|
145
|
+
const sheetRightPt = sheetLeftPt + grid.reduce((sum, w) => sum + w, 0);
|
|
146
|
+
const overlayAt = (leftPt) => sheet ? { overlay: {
|
|
147
|
+
sheetLeftPt: pt(sheetLeftPt),
|
|
148
|
+
widthPt: pt(sheetRightPt - sheetLeftPt),
|
|
149
|
+
leftPt: pt(leftPt),
|
|
150
|
+
drawings: []
|
|
151
|
+
} } : {};
|
|
152
|
+
const underDrawing = (row) => {
|
|
153
|
+
const top = row.properties.sheetTopPt;
|
|
154
|
+
if (top === void 0 || !sheet) return false;
|
|
155
|
+
const bottom = top + (row.properties.height ?? 0);
|
|
156
|
+
return sheet.drawings.some((d) => d.xPt < sheetRightPt && d.xPt + d.widthPt > sheetLeftPt && d.yPt < bottom && d.yPt + d.heightPt > top);
|
|
157
|
+
};
|
|
135
158
|
const bandRowNumbers = [...headings?.rowNumbers ?? []];
|
|
136
159
|
const bandRows = rows.map((row, rowIndex) => {
|
|
137
160
|
const cells = sliceRowCells(row.cells, band);
|
|
@@ -144,7 +167,7 @@ function bandedTables(rows, columnWidths, bands, properties, titleRowIndex = -1,
|
|
|
144
167
|
};
|
|
145
168
|
});
|
|
146
169
|
if (bandIndex > 0 && !(bandLeft < drawingReachTwips) && !bandRows.some(rowDrawsSomething)) return [];
|
|
147
|
-
while (bandRows.length > 1 && !rowDrawsSomething(bandRows[bandRows.length - 1])) {
|
|
170
|
+
while (bandRows.length > 1 && !rowDrawsSomething(bandRows[bandRows.length - 1]) && !underDrawing(bandRows[bandRows.length - 1])) {
|
|
148
171
|
bandRows.pop();
|
|
149
172
|
bandRowNumbers.pop();
|
|
150
173
|
}
|
|
@@ -158,11 +181,12 @@ function bandedTables(rows, columnWidths, bands, properties, titleRowIndex = -1,
|
|
|
158
181
|
rows: bandRows,
|
|
159
182
|
numbers: bandRowNumbers
|
|
160
183
|
}]).map((part) => {
|
|
161
|
-
const headed = headings ? withHeadingBand(part.rows, bandTwips, headings.
|
|
184
|
+
const headed = headings ? withHeadingBand(part.rows, bandTwips, headings.columns.slice(band.start, band.end + 1), part.numbers, headings.band) : void 0;
|
|
162
185
|
return {
|
|
163
186
|
kind: "table",
|
|
164
187
|
table: {
|
|
165
188
|
properties,
|
|
189
|
+
...overlayAt(headed ? twipsToPt(headings?.band?.colTwips ?? 460) : 0),
|
|
166
190
|
grid: headed ? headed.widths.map((w) => twipsToPt(w)) : grid,
|
|
167
191
|
rows: headed ? leadWithBandBreak(headed.rows) : part.rows
|
|
168
192
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveInternalEntities } from "../core/opc/xml-entities.js";
|
|
2
|
+
import { richRun } from "./shared-strings-parser.js";
|
|
2
3
|
import { XMLParser } from "fast-xml-parser";
|
|
3
4
|
//#region src/excel/comments-parser.ts
|
|
4
5
|
var decoder = new TextDecoder("utf-8");
|
|
@@ -32,10 +33,12 @@ function parseLegacyComments(data) {
|
|
|
32
33
|
const author = Number.isInteger(authorIdx) ? authors[authorIdx] : void 0;
|
|
33
34
|
const text = stripAuthorPrefix(richText(obj["text"]), author);
|
|
34
35
|
if (text.length === 0 && !author) continue;
|
|
36
|
+
const runs = richRuns(obj["text"]);
|
|
35
37
|
out.push({
|
|
36
38
|
ref,
|
|
37
39
|
...author ? { author } : {},
|
|
38
40
|
text,
|
|
41
|
+
...runs.length > 0 ? { runs } : {},
|
|
39
42
|
threaded: false
|
|
40
43
|
});
|
|
41
44
|
}
|
|
@@ -95,6 +98,22 @@ function richText(node) {
|
|
|
95
98
|
if (direct) return direct;
|
|
96
99
|
return toArray(obj["r"]).map((r) => textOf(asObject(r)?.["t"])).join("");
|
|
97
100
|
}
|
|
101
|
+
function richRuns(node) {
|
|
102
|
+
const obj = asObject(node);
|
|
103
|
+
if (!obj) {
|
|
104
|
+
const plain = textOf(node);
|
|
105
|
+
return plain ? [{ text: plain }] : [];
|
|
106
|
+
}
|
|
107
|
+
const direct = textOf(obj["t"]);
|
|
108
|
+
if (direct) return [{ text: direct }];
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const r of toArray(obj["r"])) {
|
|
111
|
+
const run = asObject(r);
|
|
112
|
+
const text = textOf(run?.["t"]);
|
|
113
|
+
if (text.length > 0) out.push(richRun(text, run?.["rPr"]));
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
98
117
|
function stripAuthorPrefix(text, author) {
|
|
99
118
|
if (!author) return text;
|
|
100
119
|
const prefix = `${author}:`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CellIcon } from '../core/document-model/index.js';
|
|
2
|
-
import { ConditionalFormat, DefinedName, WorksheetCell, XlsxBorder, XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
2
|
+
import { ConditionalFormat, DefinedName, ExcelTable, WorksheetCell, XlsxBorder, XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* A resolved per-cell override the print model layers over a cell's base format: a
|
|
5
5
|
* solid highlight fill, font tweaks, an in-cell data bar (fraction of the cell
|
|
@@ -25,12 +25,14 @@ export interface CfOverride {
|
|
|
25
25
|
readonly colorHex: string;
|
|
26
26
|
readonly startFraction?: number;
|
|
27
27
|
readonly negative?: boolean;
|
|
28
|
+
readonly solid?: boolean;
|
|
28
29
|
};
|
|
29
30
|
readonly icon?: CellIcon;
|
|
30
31
|
/**
|
|
31
32
|
* §18.3.1.28 `<dataBar showValue="0">` — the cell shows its BAR and not its
|
|
32
33
|
* number. Excel's "Show Bar Only": the figure would otherwise sit on top of
|
|
33
|
-
* its own gauge.
|
|
34
|
+
* its own gauge. §18.3.1.49 `<iconSet showValue="0">` is the same for an
|
|
35
|
+
* icon: "Show Icon Only".
|
|
34
36
|
*/
|
|
35
37
|
readonly hideValue?: boolean;
|
|
36
38
|
}
|
|
@@ -69,11 +71,13 @@ blank?: boolean) => CfOverride | undefined;
|
|
|
69
71
|
* driving `TODAY()`/`NOW()` and the `timePeriod` windows;
|
|
70
72
|
* absent ⇒ those constructs no-op (deterministic output).
|
|
71
73
|
* @param sheetGrids The whole workbook, for an `expression` rule that reaches
|
|
72
|
-
* another sheet (`Sheet2!A1`)
|
|
74
|
+
* another sheet (`Sheet2!A1`), a defined name or a table
|
|
75
|
+
* by its columns (`tblIncome[Amount]`); absent ⇒
|
|
73
76
|
* same-sheet references only.
|
|
74
77
|
* @param currentSheet The rule sheet's name, for resolving sheet-local names.
|
|
75
78
|
* @param definedNames The workbook defined names visible to `expression` rules.
|
|
76
79
|
*/
|
|
77
80
|
export declare function buildConditionalFormatter(conditionalFormats: ReadonlyArray<ConditionalFormat> | undefined, styles: XlsxStyles, cells: ReadonlyArray<WorksheetCell>, resolveText?: (cell: WorksheetCell) => string, date1904?: boolean, now?: Date, sheetGrids?: ReadonlyMap<string, {
|
|
78
81
|
readonly cells: ReadonlyArray<WorksheetCell>;
|
|
82
|
+
readonly tables?: ReadonlyArray<ExcelTable>;
|
|
79
83
|
}>, currentSheet?: string, definedNames?: ReadonlyArray<DefinedName>): CellConditionalFormatter | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { serialFromDate, timePeriodMatches } from "./formula/dates.js";
|
|
2
|
-
import { BLANK, bool, err, num, str } from "./formula/value.js";
|
|
2
|
+
import { BLANK, bool, err, num, str, toNumber } from "./formula/value.js";
|
|
3
3
|
import { NO_SHIFT, evaluate } from "./formula/eval.js";
|
|
4
4
|
import { compileFormula, evaluateToBool } from "./formula/index.js";
|
|
5
5
|
//#region src/excel/conditional-format.ts
|
|
@@ -23,7 +23,8 @@ import { compileFormula, evaluateToBool } from "./formula/index.js";
|
|
|
23
23
|
* driving `TODAY()`/`NOW()` and the `timePeriod` windows;
|
|
24
24
|
* absent ⇒ those constructs no-op (deterministic output).
|
|
25
25
|
* @param sheetGrids The whole workbook, for an `expression` rule that reaches
|
|
26
|
-
* another sheet (`Sheet2!A1`)
|
|
26
|
+
* another sheet (`Sheet2!A1`), a defined name or a table
|
|
27
|
+
* by its columns (`tblIncome[Amount]`); absent ⇒
|
|
27
28
|
* same-sheet references only.
|
|
28
29
|
* @param currentSheet The rule sheet's name, for resolving sheet-local names.
|
|
29
30
|
* @param definedNames The workbook defined names visible to `expression` rules.
|
|
@@ -31,21 +32,40 @@ import { compileFormula, evaluateToBool } from "./formula/index.js";
|
|
|
31
32
|
function buildConditionalFormatter(conditionalFormats, styles, cells, resolveText, date1904 = false, now, sheetGrids, currentSheet, definedNames) {
|
|
32
33
|
if (!conditionalFormats || conditionalFormats.length === 0) return void 0;
|
|
33
34
|
const dxfs = styles.dxfs ?? [];
|
|
35
|
+
const hasExpr = conditionalFormats.some((cf) => cf.rules.some((r) => r.type === "expression"));
|
|
36
|
+
const formulaStops = conditionalFormats.some((cf) => cf.rules.some((r) => (r.type === "colorScale" || r.type === "dataBar" || r.type === "iconSet") && r.cfvos.some((c) => c.val !== void 0 && !Number.isFinite(Number(c.val)))));
|
|
37
|
+
const nowSerial = now !== void 0 ? serialFromDate(now, date1904) : void 0;
|
|
38
|
+
const cross = (hasExpr || formulaStops) && sheetGrids ? {
|
|
39
|
+
sheets: [...sheetGrids].map(([name, ws]) => ({
|
|
40
|
+
name,
|
|
41
|
+
cells: ws.cells,
|
|
42
|
+
...ws.tables ? { tables: ws.tables } : {}
|
|
43
|
+
})),
|
|
44
|
+
...currentSheet !== void 0 ? { currentSheet } : {},
|
|
45
|
+
definedNames: definedNames ?? []
|
|
46
|
+
} : void 0;
|
|
47
|
+
const ctx = hasExpr || formulaStops ? buildEvalContext(cells, resolveText, nowSerial, date1904, cross) : void 0;
|
|
48
|
+
const stop = ctx ? (formula) => {
|
|
49
|
+
const compiled = compileFormula(formula.startsWith("=") ? formula.slice(1) : formula);
|
|
50
|
+
if (!compiled) return void 0;
|
|
51
|
+
const n = toNumber(evaluate(compiled.ast, ctx, NO_SHIFT), ctx);
|
|
52
|
+
return typeof n === "number" && Number.isFinite(n) ? n : void 0;
|
|
53
|
+
} : void 0;
|
|
34
54
|
const flat = [];
|
|
35
55
|
for (const cf of conditionalFormats) for (const rule of cf.rules) if (rule.type === "colorScale") flat.push({
|
|
36
56
|
ranges: cf.ranges,
|
|
37
57
|
rule,
|
|
38
|
-
scale: resolveColorScale(rule, cf.ranges, cells)
|
|
58
|
+
scale: resolveColorScale(rule, cf.ranges, cells, stop)
|
|
39
59
|
});
|
|
40
60
|
else if (rule.type === "dataBar") flat.push({
|
|
41
61
|
ranges: cf.ranges,
|
|
42
62
|
rule,
|
|
43
|
-
bar: resolveDataBar(rule, cf.ranges, cells)
|
|
63
|
+
bar: resolveDataBar(rule, cf.ranges, cells, stop)
|
|
44
64
|
});
|
|
45
65
|
else if (rule.type === "iconSet") flat.push({
|
|
46
66
|
ranges: cf.ranges,
|
|
47
67
|
rule,
|
|
48
|
-
iconThresholds: resolveIconThresholds(rule, cf.ranges, cells)
|
|
68
|
+
iconThresholds: resolveIconThresholds(rule, cf.ranges, cells, stop)
|
|
49
69
|
});
|
|
50
70
|
else if (rule.type === "top10") flat.push({
|
|
51
71
|
ranges: cf.ranges,
|
|
@@ -78,17 +98,6 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
78
98
|
rule
|
|
79
99
|
});
|
|
80
100
|
flat.sort((a, b) => a.rule.priority - b.rule.priority);
|
|
81
|
-
const hasExpr = flat.some((f) => f.rule.type === "expression");
|
|
82
|
-
const nowSerial = now !== void 0 ? serialFromDate(now, date1904) : void 0;
|
|
83
|
-
const cross = hasExpr && sheetGrids ? {
|
|
84
|
-
sheets: [...sheetGrids].map(([name, ws]) => ({
|
|
85
|
-
name,
|
|
86
|
-
cells: ws.cells
|
|
87
|
-
})),
|
|
88
|
-
...currentSheet !== void 0 ? { currentSheet } : {},
|
|
89
|
-
definedNames: definedNames ?? []
|
|
90
|
-
} : void 0;
|
|
91
|
-
const ctx = hasExpr ? buildEvalContext(cells, resolveText, nowSerial, date1904, cross) : void 0;
|
|
92
101
|
return (row, col, value, text, blank) => {
|
|
93
102
|
if (!hasExpr && value === void 0 && (text === void 0 || text.length === 0)) return;
|
|
94
103
|
const dupKey = blank === true ? void 0 : dupKeyOf(value, text);
|
|
@@ -124,6 +133,7 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
124
133
|
if (!icon && value !== void 0 && iconThresholds) {
|
|
125
134
|
const bucket = iconBucket(iconThresholds, value);
|
|
126
135
|
icon = iconToCell(rule.iconSet, iconThresholds.length, bucket, rule.reverse ?? false);
|
|
136
|
+
if (rule.showValue === false) hideValue = true;
|
|
127
137
|
}
|
|
128
138
|
break;
|
|
129
139
|
case "top10":
|
|
@@ -206,12 +216,34 @@ function buildEvalContext(cells, resolveText, nowSerial, date1904, cross) {
|
|
|
206
216
|
const key = dn.name.toLowerCase();
|
|
207
217
|
if (dn.localSheetId !== void 0 || !nameMap.has(key)) nameMap.set(key, dn);
|
|
208
218
|
}
|
|
219
|
+
const tables = /* @__PURE__ */ new Map();
|
|
220
|
+
const onThisSheet = [];
|
|
221
|
+
cross.sheets.forEach((s, i) => {
|
|
222
|
+
for (const t of s.tables ?? []) {
|
|
223
|
+
const table = {
|
|
224
|
+
...i !== currentIdx ? { sheet: i } : {},
|
|
225
|
+
ref: {
|
|
226
|
+
r0: t.ref.startRow,
|
|
227
|
+
c0: t.ref.startColumn,
|
|
228
|
+
r1: t.ref.endRow,
|
|
229
|
+
c1: t.ref.endColumn
|
|
230
|
+
},
|
|
231
|
+
headerRowCount: t.headerRowCount,
|
|
232
|
+
totalsRowCount: t.totalsRowCount ?? 0,
|
|
233
|
+
columns: t.columns ?? []
|
|
234
|
+
};
|
|
235
|
+
if (t.name !== void 0) tables.set(t.name.toLowerCase(), table);
|
|
236
|
+
if (i === currentIdx) onThisSheet.push(table);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
209
239
|
const resolving = /* @__PURE__ */ new Set();
|
|
210
240
|
const ctx = {
|
|
211
241
|
...base,
|
|
212
242
|
sheetIndex: (name) => sheetByName.get(name.toLowerCase()),
|
|
213
243
|
getCellOn: (sheet, row, col) => sheetData(sheet).byKey.get(row * COLS + col) ?? BLANK,
|
|
214
244
|
eachCellOn: (sheet, rect, visit) => eachInMap(sheetData(sheet), rect, visit),
|
|
245
|
+
table: (name) => tables.get(name.toLowerCase()),
|
|
246
|
+
tableAt: (row, col) => onThisSheet.find((t) => row >= t.ref.r0 && row <= t.ref.r1 && col >= t.ref.c0 && col <= t.ref.c1),
|
|
215
247
|
resolveName: (rawName) => {
|
|
216
248
|
const key = rawName.toLowerCase();
|
|
217
249
|
const dn = nameMap.get(key);
|
|
@@ -222,7 +254,6 @@ function buildEvalContext(cells, resolveText, nowSerial, date1904, cross) {
|
|
|
222
254
|
if (ast.k === "num") return num(ast.v);
|
|
223
255
|
if (ast.k === "str") return str(ast.v);
|
|
224
256
|
if (ast.k === "bool") return bool(ast.v);
|
|
225
|
-
if (ast.k !== "cell" && ast.k !== "range") return void 0;
|
|
226
257
|
resolving.add(key);
|
|
227
258
|
try {
|
|
228
259
|
return evaluate(ast, ctx, NO_SHIFT);
|
|
@@ -358,7 +389,7 @@ function collectRangeValues(cells, ranges) {
|
|
|
358
389
|
}
|
|
359
390
|
return out;
|
|
360
391
|
}
|
|
361
|
-
function resolveColorScale(rule, ranges, cells) {
|
|
392
|
+
function resolveColorScale(rule, ranges, cells, stop) {
|
|
362
393
|
const vals = collectRangeValues(cells, ranges);
|
|
363
394
|
if (vals.length === 0) return void 0;
|
|
364
395
|
const sorted = [...vals].sort((a, b) => a - b);
|
|
@@ -366,7 +397,7 @@ function resolveColorScale(rule, ranges, cells) {
|
|
|
366
397
|
const vmax = sorted[sorted.length - 1];
|
|
367
398
|
const thresholds = [];
|
|
368
399
|
for (const [i, cfvo] of rule.cfvos.entries()) {
|
|
369
|
-
const t = resolveCfvo(cfvo, vmin, vmax, sorted, i === rule.cfvos.length - 1);
|
|
400
|
+
const t = resolveCfvo(cfvo, vmin, vmax, sorted, i === rule.cfvos.length - 1, stop);
|
|
370
401
|
if (t === void 0) return void 0;
|
|
371
402
|
thresholds.push(t);
|
|
372
403
|
}
|
|
@@ -383,24 +414,26 @@ function resolveColorScale(rule, ranges, cells) {
|
|
|
383
414
|
colors
|
|
384
415
|
};
|
|
385
416
|
}
|
|
386
|
-
function resolveCfvo(cfvo, vmin, vmax, sorted, isLast = false) {
|
|
417
|
+
function resolveCfvo(cfvo, vmin, vmax, sorted, isLast = false, stop) {
|
|
418
|
+
const given = () => {
|
|
419
|
+
const n = Number(cfvo.val);
|
|
420
|
+
if (Number.isFinite(n)) return n;
|
|
421
|
+
return cfvo.val !== void 0 ? stop?.(cfvo.val) : void 0;
|
|
422
|
+
};
|
|
387
423
|
switch (cfvo.type) {
|
|
388
424
|
case "min":
|
|
389
425
|
case "autoMin": return vmin;
|
|
390
426
|
case "max":
|
|
391
427
|
case "autoMax": return vmax;
|
|
392
|
-
case "num": return
|
|
393
|
-
case "formula":
|
|
394
|
-
const n = Number(cfvo.val);
|
|
395
|
-
return Number.isFinite(n) ? n : isLast ? vmax : vmin;
|
|
396
|
-
}
|
|
428
|
+
case "num": return given();
|
|
429
|
+
case "formula": return given() ?? (isLast ? vmax : vmin);
|
|
397
430
|
case "percent": {
|
|
398
|
-
const p =
|
|
399
|
-
return
|
|
431
|
+
const p = given();
|
|
432
|
+
return p !== void 0 ? vmin + (vmax - vmin) * (p / 100) : void 0;
|
|
400
433
|
}
|
|
401
434
|
case "percentile": {
|
|
402
|
-
const p =
|
|
403
|
-
return
|
|
435
|
+
const p = given();
|
|
436
|
+
return p !== void 0 ? percentile(sorted, p) : void 0;
|
|
404
437
|
}
|
|
405
438
|
}
|
|
406
439
|
}
|
|
@@ -445,50 +478,55 @@ function rgbHex([r, g, b]) {
|
|
|
445
478
|
const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0").toUpperCase();
|
|
446
479
|
return `${h(r)}${h(g)}${h(b)}`;
|
|
447
480
|
}
|
|
448
|
-
function resolveDataBar(rule, ranges, cells) {
|
|
481
|
+
function resolveDataBar(rule, ranges, cells, stop) {
|
|
449
482
|
const vals = collectRangeValues(cells, ranges);
|
|
450
483
|
if (vals.length === 0) return void 0;
|
|
451
484
|
const sorted = [...vals].sort((a, b) => a - b);
|
|
452
485
|
const vmin = sorted[0];
|
|
453
486
|
const vmax = sorted[sorted.length - 1];
|
|
454
|
-
const lower = resolveCfvo(rule.cfvos[0], vmin, vmax, sorted);
|
|
455
|
-
const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted, true);
|
|
487
|
+
const lower = resolveCfvo(rule.cfvos[0], vmin, vmax, sorted, false, stop);
|
|
488
|
+
const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted, true, stop);
|
|
456
489
|
if (lower === void 0 || upper === void 0) return void 0;
|
|
457
490
|
return {
|
|
458
491
|
lower,
|
|
459
492
|
upper,
|
|
460
493
|
colorHex: rule.colorHex,
|
|
461
494
|
negativeColorHex: "FF0000",
|
|
462
|
-
minLen: (rule.minLength ??
|
|
463
|
-
maxLen: (rule.maxLength ??
|
|
495
|
+
minLen: (rule.minLength ?? 10) / 100,
|
|
496
|
+
maxLen: (rule.maxLength ?? 90) / 100,
|
|
497
|
+
solid: rule.gradient === false
|
|
464
498
|
};
|
|
465
499
|
}
|
|
466
500
|
function dataBarBar(bar, value) {
|
|
501
|
+
const solid = bar.solid ? { solid: true } : {};
|
|
467
502
|
if (bar.lower < 0 && bar.upper > 0) {
|
|
468
503
|
const axis = -bar.lower / (bar.upper - bar.lower);
|
|
469
504
|
if (value >= 0) return {
|
|
470
505
|
fraction: value / bar.upper * (1 - axis),
|
|
471
506
|
colorHex: bar.colorHex,
|
|
472
|
-
startFraction: axis
|
|
507
|
+
startFraction: axis,
|
|
508
|
+
...solid
|
|
473
509
|
};
|
|
474
510
|
const w = Math.abs(value) / Math.abs(bar.lower) * axis;
|
|
475
511
|
return {
|
|
476
512
|
fraction: w,
|
|
477
513
|
colorHex: bar.negativeColorHex,
|
|
478
514
|
startFraction: axis - w,
|
|
479
|
-
negative: true
|
|
515
|
+
negative: true,
|
|
516
|
+
...solid
|
|
480
517
|
};
|
|
481
518
|
}
|
|
482
519
|
return {
|
|
483
520
|
fraction: dataBarFraction(bar, value),
|
|
484
|
-
colorHex: bar.colorHex
|
|
521
|
+
colorHex: bar.colorHex,
|
|
522
|
+
...solid
|
|
485
523
|
};
|
|
486
524
|
}
|
|
487
525
|
function dataBarFraction(bar, value) {
|
|
488
526
|
const t = bar.upper > bar.lower ? Math.min(1, Math.max(0, (value - bar.lower) / (bar.upper - bar.lower))) : value >= bar.upper ? 1 : 0;
|
|
489
527
|
return bar.minLen + t * (bar.maxLen - bar.minLen);
|
|
490
528
|
}
|
|
491
|
-
function resolveIconThresholds(rule, ranges, cells) {
|
|
529
|
+
function resolveIconThresholds(rule, ranges, cells, stop) {
|
|
492
530
|
const vals = collectRangeValues(cells, ranges);
|
|
493
531
|
if (vals.length === 0) return void 0;
|
|
494
532
|
const sorted = [...vals].sort((a, b) => a - b);
|
|
@@ -496,7 +534,7 @@ function resolveIconThresholds(rule, ranges, cells) {
|
|
|
496
534
|
const vmax = sorted[sorted.length - 1];
|
|
497
535
|
const out = [];
|
|
498
536
|
for (const cfvo of rule.cfvos) {
|
|
499
|
-
const t = resolveCfvo(cfvo, vmin, vmax, sorted);
|
|
537
|
+
const t = resolveCfvo(cfvo, vmin, vmax, sorted, false, stop);
|
|
500
538
|
if (t === void 0) return void 0;
|
|
501
539
|
out.push(t);
|
|
502
540
|
}
|
|
@@ -45,4 +45,22 @@ export interface EvalContext {
|
|
|
45
45
|
* Rect, optionally on another sheet) or a literal scalar; undefined ⇒ `#NAME?`.
|
|
46
46
|
*/
|
|
47
47
|
readonly resolveName?: (name: string) => FValue | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* §18.5 — a table by its name (case-insensitive), for a structured reference
|
|
50
|
+
* (`tblIncome[Amount]`); undefined ⇒ `#REF!`.
|
|
51
|
+
*/
|
|
52
|
+
readonly table?: (name: string) => FormulaTable | undefined;
|
|
53
|
+
/** The table a cell stands in, for a reference that names none (`[@Amount]`). */
|
|
54
|
+
readonly tableAt?: (row: number, col: number) => FormulaTable | undefined;
|
|
55
|
+
}
|
|
56
|
+
/** What a structured reference needs of a table (§18.5.1.2). */
|
|
57
|
+
export interface FormulaTable {
|
|
58
|
+
/** The sheet it is on, when not the formula's own; undefined ⇒ the formula's sheet. */
|
|
59
|
+
readonly sheet?: number;
|
|
60
|
+
/** Its whole range — headers, data and totals. */
|
|
61
|
+
readonly ref: Rect;
|
|
62
|
+
readonly headerRowCount: number;
|
|
63
|
+
readonly totalsRowCount: number;
|
|
64
|
+
/** Its columns' names, left to right. */
|
|
65
|
+
readonly columns: ReadonlyArray<string>;
|
|
48
66
|
}
|
|
@@ -56,6 +56,7 @@ function evaluate(ast, ctx, shift) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
case "name": return ctx.resolveName?.(ast.name) ?? err("#NAME?");
|
|
59
|
+
case "sref": return structuredRef(ast.ref, ctx, shift);
|
|
59
60
|
case "array": return {
|
|
60
61
|
t: "arr",
|
|
61
62
|
rows: ast.rows.map((row) => row.map((el) => deref(evaluate(el, ctx, shift), ctx)))
|
|
@@ -69,6 +70,56 @@ function evaluate(ast, ctx, shift) {
|
|
|
69
70
|
case "call": return callFn(ast.name, ast.args, (a) => evaluate(a, ctx, shift), ctx, shift);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* §18.17.6.4 — the cells a structured reference names: the parts of the table
|
|
75
|
+
* it asks for (its data rows when it names none), across the columns it names
|
|
76
|
+
* (all of them when it names none). `#This Row` is the current cell's row.
|
|
77
|
+
*
|
|
78
|
+
* @param ref The reference.
|
|
79
|
+
* @param ctx The evaluation context, which knows the tables.
|
|
80
|
+
* @param shift The current cell, for `#This Row` and for a reference naming no table.
|
|
81
|
+
* @returns The range, or `#REF!` for a table, column or part that is not there.
|
|
82
|
+
*/
|
|
83
|
+
function structuredRef(ref, ctx, shift) {
|
|
84
|
+
const table = ref.table.length > 0 ? ctx.table?.(ref.table) : shift.curRow !== void 0 && shift.curCol !== void 0 ? ctx.tableAt?.(shift.curRow, shift.curCol) : void 0;
|
|
85
|
+
if (!table) return err("#REF!");
|
|
86
|
+
const { r0, c0, r1, c1 } = table.ref;
|
|
87
|
+
const headerEnd = r0 + table.headerRowCount - 1;
|
|
88
|
+
const totalsStart = r1 - table.totalsRowCount + 1;
|
|
89
|
+
const rows = (area) => {
|
|
90
|
+
switch (area) {
|
|
91
|
+
case "#All": return [r0, r1];
|
|
92
|
+
case "#Data": return [headerEnd + 1, totalsStart - 1];
|
|
93
|
+
case "#Headers": return table.headerRowCount > 0 ? [r0, headerEnd] : void 0;
|
|
94
|
+
case "#Totals": return table.totalsRowCount > 0 ? [totalsStart, r1] : void 0;
|
|
95
|
+
case "#This Row": return shift.curRow !== void 0 ? [shift.curRow, shift.curRow] : void 0;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const spans = (ref.areas.length > 0 ? ref.areas : ["#Data"]).map(rows);
|
|
99
|
+
if (spans.some((s) => s === void 0 || s[1] < s[0])) return err("#REF!");
|
|
100
|
+
const top = Math.min(...spans.map((s) => s[0]));
|
|
101
|
+
const bottom = Math.max(...spans.map((s) => s[1]));
|
|
102
|
+
let left = c0;
|
|
103
|
+
let right = c1;
|
|
104
|
+
if (ref.columns) {
|
|
105
|
+
const at = (name) => table.columns.findIndex((c) => c.toLowerCase() === name.toLowerCase());
|
|
106
|
+
const a = at(ref.columns[0]);
|
|
107
|
+
const b = at(ref.columns[1]);
|
|
108
|
+
if (a < 0 || b < 0) return err("#REF!");
|
|
109
|
+
left = c0 + Math.min(a, b);
|
|
110
|
+
right = c0 + Math.max(a, b);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
t: "ref",
|
|
114
|
+
rect: {
|
|
115
|
+
r0: top,
|
|
116
|
+
c0: left,
|
|
117
|
+
r1: bottom,
|
|
118
|
+
c1: right
|
|
119
|
+
},
|
|
120
|
+
...table.sheet !== void 0 ? { sheet: table.sheet } : {}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
72
123
|
function resolveRef(ref, shift) {
|
|
73
124
|
const row = ref.row.abs ? ref.row.index : ref.row.index + shift.dRow;
|
|
74
125
|
const col = ref.col.abs ? ref.col.index : ref.col.index + shift.dCol;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Ast } from './parser.js';
|
|
2
2
|
import { EvalContext } from './context.js';
|
|
3
3
|
import { Shift } from './eval.js';
|
|
4
|
-
export type { EvalContext } from './context.js';
|
|
4
|
+
export type { EvalContext, FormulaTable } from './context.js';
|
|
5
5
|
export type { Shift } from './eval.js';
|
|
6
6
|
export type { Scalar, FValue, Rect, FErr } from './value.js';
|
|
7
|
-
export { num, str, bool, err, BLANK } from './value.js';
|
|
7
|
+
export { num, str, bool, err, BLANK, toNumber } from './value.js';
|
|
8
8
|
export { serialFromDate, serialToParts, timePeriodMatches } from './dates.js';
|
|
9
9
|
export { NO_SHIFT, evaluate } from './eval.js';
|
|
10
10
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The lexical categories the {@link tokenize} pass emits. */
|
|
2
|
-
export type TokenKind = 'num' | 'str' | 'err' | 'word' | 'sheetq' | 'op' | 'eof';
|
|
2
|
+
export type TokenKind = 'num' | 'str' | 'err' | 'word' | 'sheetq' | 'sref' | 'op' | 'eof';
|
|
3
3
|
/** One token: its {@link TokenKind} and the matched source text. */
|
|
4
4
|
export interface Token {
|
|
5
5
|
readonly kind: TokenKind;
|
|
@@ -21,6 +21,29 @@ function isWordPart(ch) {
|
|
|
21
21
|
return isWordStart(ch) || isDigit(ch) || ch === ".";
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
+
* Where the bracket group that opens at `start` closes: past its matching `]`,
|
|
25
|
+
* nested groups included. A `'` escapes the character after it — a column
|
|
26
|
+
* named `Q[1]` is written `[Q'[1']]`.
|
|
27
|
+
*
|
|
28
|
+
* @param src The formula source.
|
|
29
|
+
* @param start The index of the opening `[`.
|
|
30
|
+
* @returns The index just past the closing `]`.
|
|
31
|
+
* @throws LexError when the group never closes.
|
|
32
|
+
*/
|
|
33
|
+
function bracketsEnd(src, start) {
|
|
34
|
+
let depth = 0;
|
|
35
|
+
for (let j = start; j < src.length; j++) {
|
|
36
|
+
const c = src[j];
|
|
37
|
+
if (c === "'") {
|
|
38
|
+
j++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (c === "[") depth++;
|
|
42
|
+
else if (c === "]" && --depth === 0) return j + 1;
|
|
43
|
+
}
|
|
44
|
+
throw new LexError("unclosed [");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
24
47
|
* Tokenize a formula string into a flat token stream terminated by an `eof`
|
|
25
48
|
* token. Whitespace separates tokens but is otherwise dropped.
|
|
26
49
|
*
|
|
@@ -122,6 +145,15 @@ function tokenize(src) {
|
|
|
122
145
|
if (isWordStart(ch)) {
|
|
123
146
|
let j = i + 1;
|
|
124
147
|
while (j < n && isWordPart(src[j])) j++;
|
|
148
|
+
if (src[j] === "[") {
|
|
149
|
+
const end = bracketsEnd(src, j);
|
|
150
|
+
out.push({
|
|
151
|
+
kind: "sref",
|
|
152
|
+
text: src.slice(i, end)
|
|
153
|
+
});
|
|
154
|
+
i = end;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
125
157
|
out.push({
|
|
126
158
|
kind: "word",
|
|
127
159
|
text: src.slice(i, j)
|
|
@@ -129,6 +161,15 @@ function tokenize(src) {
|
|
|
129
161
|
i = j;
|
|
130
162
|
continue;
|
|
131
163
|
}
|
|
164
|
+
if (ch === "[") {
|
|
165
|
+
const end = bracketsEnd(src, i);
|
|
166
|
+
out.push({
|
|
167
|
+
kind: "sref",
|
|
168
|
+
text: src.slice(i, end)
|
|
169
|
+
});
|
|
170
|
+
i = end;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
132
173
|
if (ch === "<" && src[i + 1] === "=") {
|
|
133
174
|
out.push({
|
|
134
175
|
kind: "op",
|
|
@@ -15,6 +15,19 @@ export interface CellRef {
|
|
|
15
15
|
readonly col: Axis;
|
|
16
16
|
readonly row: Axis;
|
|
17
17
|
}
|
|
18
|
+
/** A table's part a structured reference names (§18.17.6.4); none named ⇒ `#Data`. */
|
|
19
|
+
export type TableArea = '#All' | '#Data' | '#Headers' | '#Totals' | '#This Row';
|
|
20
|
+
/**
|
|
21
|
+
* §18.17.6.4 — a structured reference: the table (empty for the one the cell
|
|
22
|
+
* is in), the parts of it, and the columns, first to last (all of them when
|
|
23
|
+
* undefined). `tblIncome[Amount]` is `{ table: 'tblIncome', areas: [],
|
|
24
|
+
* columns: ['Amount', 'Amount'] }`.
|
|
25
|
+
*/
|
|
26
|
+
export interface StructuredRef {
|
|
27
|
+
readonly table: string;
|
|
28
|
+
readonly areas: ReadonlyArray<TableArea>;
|
|
29
|
+
readonly columns?: readonly [string, string];
|
|
30
|
+
}
|
|
18
31
|
/** The formula syntax tree — a discriminated union over the node kind `k`. */
|
|
19
32
|
export type Ast = {
|
|
20
33
|
readonly k: 'num';
|
|
@@ -40,6 +53,9 @@ export type Ast = {
|
|
|
40
53
|
} | {
|
|
41
54
|
readonly k: 'name';
|
|
42
55
|
readonly name: string;
|
|
56
|
+
} | {
|
|
57
|
+
readonly k: 'sref';
|
|
58
|
+
readonly ref: StructuredRef;
|
|
43
59
|
} | {
|
|
44
60
|
readonly k: 'array';
|
|
45
61
|
readonly rows: ReadonlyArray<ReadonlyArray<Ast>>;
|
|
@@ -75,3 +91,12 @@ export declare class ParseError extends Error {
|
|
|
75
91
|
* @throws ParseError on a malformed formula (or {@link LexError} from tokenizing).
|
|
76
92
|
*/
|
|
77
93
|
export declare function parse(src: string): Ast;
|
|
94
|
+
/**
|
|
95
|
+
* §18.17.6.4 — read a structured reference's text: `Table[Col]`, `Table[]`,
|
|
96
|
+
* `Table[#Totals]`, `Table[[#Headers],[A]:[C]]`, `[@Col]`, `[@[Col 2]]`.
|
|
97
|
+
*
|
|
98
|
+
* @param text The token: the table's name, if any, and its brackets.
|
|
99
|
+
* @returns The reference.
|
|
100
|
+
* @throws ParseError when the brackets name nothing a table has.
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseStructuredRef(text: string): StructuredRef;
|