@lotics/xlsx 0.1.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.
Files changed (90) hide show
  1. package/package.json +30 -0
  2. package/src/auto_sum.test.ts +71 -0
  3. package/src/auto_sum.ts +83 -0
  4. package/src/canvas_cf.ts +135 -0
  5. package/src/canvas_chart.ts +687 -0
  6. package/src/canvas_fill.ts +156 -0
  7. package/src/canvas_images.ts +99 -0
  8. package/src/canvas_layout.test.ts +159 -0
  9. package/src/canvas_layout.ts +239 -0
  10. package/src/canvas_renderer.ts +1010 -0
  11. package/src/canvas_shape.ts +242 -0
  12. package/src/canvas_sparkline.ts +163 -0
  13. package/src/canvas_text.ts +454 -0
  14. package/src/cell_editor.ts +558 -0
  15. package/src/clipboard.test.ts +145 -0
  16. package/src/clipboard.ts +341 -0
  17. package/src/command_history.ts +102 -0
  18. package/src/csv_parser.test.ts +130 -0
  19. package/src/csv_parser.ts +172 -0
  20. package/src/data_validation.test.ts +95 -0
  21. package/src/data_validation.ts +114 -0
  22. package/src/editing_layer.test.ts +309 -0
  23. package/src/excel_cf_evaluate.test.ts +293 -0
  24. package/src/excel_cf_evaluate.ts +719 -0
  25. package/src/excel_cf_icons.ts +108 -0
  26. package/src/excel_cf_types.ts +67 -0
  27. package/src/excel_numfmt.test.ts +607 -0
  28. package/src/excel_numfmt.ts +1033 -0
  29. package/src/excel_parser.test.ts +1061 -0
  30. package/src/excel_parser.ts +393 -0
  31. package/src/excel_pattern_fills.ts +64 -0
  32. package/src/excel_table_styles.ts +160 -0
  33. package/src/excel_utils.test.ts +108 -0
  34. package/src/excel_utils.ts +162 -0
  35. package/src/fast-formula-parser.d.ts +53 -0
  36. package/src/fill_logic.ts +257 -0
  37. package/src/fill_patterns.test.ts +90 -0
  38. package/src/fill_patterns.ts +71 -0
  39. package/src/flash_fill.test.ts +75 -0
  40. package/src/flash_fill.ts +221 -0
  41. package/src/formula_deps.ts +189 -0
  42. package/src/formula_engine.test.ts +348 -0
  43. package/src/formula_engine.ts +401 -0
  44. package/src/formula_highlight.test.ts +81 -0
  45. package/src/formula_highlight.ts +139 -0
  46. package/src/formula_suggest.ts +100 -0
  47. package/src/hidden_sheets.test.ts +49 -0
  48. package/src/index.ts +149 -0
  49. package/src/keyboard_nav.test.ts +394 -0
  50. package/src/keyboard_nav.ts +891 -0
  51. package/src/move_logic.ts +63 -0
  52. package/src/numfmt_type.test.ts +158 -0
  53. package/src/numfmt_type.ts +231 -0
  54. package/src/ooxml_cell_format.ts +70 -0
  55. package/src/ooxml_chart.test.ts +85 -0
  56. package/src/ooxml_chart.ts +347 -0
  57. package/src/ooxml_chart_types.ts +207 -0
  58. package/src/ooxml_color.ts +339 -0
  59. package/src/ooxml_drawing.test.ts +87 -0
  60. package/src/ooxml_drawing.ts +287 -0
  61. package/src/ooxml_pivot.test.ts +195 -0
  62. package/src/ooxml_pivot.ts +468 -0
  63. package/src/ooxml_pivot_types.ts +165 -0
  64. package/src/ooxml_shape.ts +355 -0
  65. package/src/ooxml_sheet.ts +1271 -0
  66. package/src/ooxml_styles.ts +556 -0
  67. package/src/ooxml_table.test.ts +70 -0
  68. package/src/ooxml_table.ts +131 -0
  69. package/src/ooxml_workbook.test.ts +40 -0
  70. package/src/ooxml_workbook.ts +259 -0
  71. package/src/ooxml_zip.ts +33 -0
  72. package/src/pivot_model.ts +237 -0
  73. package/src/pivot_recompute.test.ts +210 -0
  74. package/src/pivot_recompute.ts +413 -0
  75. package/src/selection_model.ts +364 -0
  76. package/src/spreadsheet_commands.test.ts +772 -0
  77. package/src/spreadsheet_commands.ts +1805 -0
  78. package/src/structured_refs.test.ts +128 -0
  79. package/src/structured_refs.ts +160 -0
  80. package/src/style_helpers.test.ts +33 -0
  81. package/src/style_helpers.ts +30 -0
  82. package/src/template_builder.test.ts +139 -0
  83. package/src/template_builder.ts +36 -0
  84. package/src/types.ts +239 -0
  85. package/src/workbook_model.test.ts +536 -0
  86. package/src/workbook_model.ts +911 -0
  87. package/src/xlsx_round_trip.test.ts +279 -0
  88. package/src/xlsx_writer.test.ts +488 -0
  89. package/src/xlsx_writer.ts +1549 -0
  90. package/src/xml_entities.ts +23 -0
@@ -0,0 +1,347 @@
1
+ // =============================================================================
2
+ // OOXML Chart Parser
3
+ // =============================================================================
4
+ //
5
+ // Parses xl/charts/chartN.xml files into ParsedChart objects.
6
+ // Handles: bar, column, line, pie, doughnut, area, scatter, bubble, radar,
7
+ // stock, combo charts. Extracts series data, axes, legend, formatting.
8
+ // =============================================================================
9
+
10
+ import type { ParsedChart, ChartType, ChartSeries, ChartAxis } from "./ooxml_chart_types";
11
+
12
+ // =============================================================================
13
+ // Parser
14
+ // =============================================================================
15
+
16
+ /**
17
+ * Parse a chart XML string into a ParsedChart (without anchor — caller sets it).
18
+ */
19
+ export function parseChartXml(
20
+ xml: string,
21
+ parseXml: (xml: string) => Record<string, unknown>,
22
+ ): Omit<ParsedChart, "anchor"> {
23
+ const doc = parseXml(xml);
24
+ const chartSpace = (getObj(doc, "c:chartSpace") ?? getObj(doc, "chartSpace") ?? doc) as Record<string, unknown>;
25
+ const chart = (getObj(chartSpace, "c:chart") ?? getObj(chartSpace, "chart") ?? chartSpace) as Record<string, unknown>;
26
+ const plotArea = (getObj(chart, "c:plotArea") ?? getObj(chart, "plotArea") ?? chart) as Record<string, unknown>;
27
+
28
+ // Detect chart type from plot area child elements
29
+ const { chartType, series, categories } = parseChartData(plotArea as Record<string, unknown>);
30
+
31
+ // Title
32
+ const titleObj = getObj(chart as Record<string, unknown>, "c:title")
33
+ ?? getObj(chart as Record<string, unknown>, "title");
34
+ const title = extractTitle(titleObj);
35
+
36
+ // Legend
37
+ const legendObj = getObj(chart as Record<string, unknown>, "c:legend")
38
+ ?? getObj(chart as Record<string, unknown>, "legend");
39
+ const legendPosition = extractLegendPosition(legendObj);
40
+
41
+ // Axes
42
+ const axes = parseAxes(plotArea as Record<string, unknown>);
43
+
44
+ return { chartType, series, title, legendPosition, axes, categories };
45
+ }
46
+
47
+ // =============================================================================
48
+ // Chart data extraction
49
+ // =============================================================================
50
+
51
+ const CHART_TYPE_MAP: Record<string, ChartType> = {
52
+ "c:barChart": "col",
53
+ "c:bar3DChart": "col",
54
+ "c:lineChart": "line",
55
+ "c:line3DChart": "line",
56
+ "c:pieChart": "pie",
57
+ "c:pie3DChart": "pie",
58
+ "c:doughnutChart": "doughnut",
59
+ "c:areaChart": "area",
60
+ "c:area3DChart": "area",
61
+ "c:scatterChart": "scatter",
62
+ "c:bubbleChart": "bubble",
63
+ "c:radarChart": "radar",
64
+ "c:stockChart": "stock",
65
+ "c:surfaceChart": "surface",
66
+ "c:surface3DChart": "surface",
67
+ "barChart": "col",
68
+ "bar3DChart": "col",
69
+ "lineChart": "line",
70
+ "line3DChart": "line",
71
+ "pieChart": "pie",
72
+ "pie3DChart": "pie",
73
+ "doughnutChart": "doughnut",
74
+ "areaChart": "area",
75
+ "area3DChart": "area",
76
+ "scatterChart": "scatter",
77
+ "bubbleChart": "bubble",
78
+ "radarChart": "radar",
79
+ "stockChart": "stock",
80
+ "surfaceChart": "surface",
81
+ "surface3DChart": "surface",
82
+ };
83
+
84
+ function parseChartData(plotArea: Record<string, unknown>): { chartType: ChartType; series: ChartSeries[]; categories?: string[] } {
85
+ // Detect combo charts — multiple chart type children in plot area
86
+ const foundTypes: { key: string; type: ChartType; node: Record<string, unknown> }[] = [];
87
+ for (const [key, type] of Object.entries(CHART_TYPE_MAP)) {
88
+ const chartNode = plotArea[key];
89
+ if (!chartNode) continue;
90
+ let chartType = type;
91
+ const obj = chartNode as Record<string, unknown>;
92
+ if ((key === "c:barChart" || key === "barChart") &&
93
+ (getAttr(obj, "c:barDir", "val") === "bar" || getAttr(obj, "barDir", "val") === "bar")) {
94
+ chartType = "bar";
95
+ }
96
+ foundTypes.push({ key, type: chartType, node: obj });
97
+ }
98
+
99
+ if (foundTypes.length === 0) {
100
+ return { chartType: "unknown", series: [] };
101
+ }
102
+
103
+ // Single chart type
104
+ if (foundTypes.length === 1) {
105
+ const { type, node } = foundTypes[0];
106
+ const series = extractSeries(node);
107
+ const categories = extractCategories(node);
108
+ return { chartType: type, series, categories };
109
+ }
110
+
111
+ // Combo chart — merge series from all types, tag each with its chart type
112
+ const allSeries: ChartSeries[] = [];
113
+ let categories: string[] | undefined;
114
+ for (const { type, node } of foundTypes) {
115
+ const series = extractSeries(node);
116
+ for (const s of series) {
117
+ s.seriesChartType = type;
118
+ }
119
+ allSeries.push(...series);
120
+ if (!categories) categories = extractCategories(node);
121
+ }
122
+
123
+ return { chartType: "combo", series: allSeries, categories };
124
+ }
125
+
126
+ function extractSeries(chartNode: Record<string, unknown>): ChartSeries[] {
127
+ const serNodes = ensureArray(chartNode["c:ser"] ?? chartNode["ser"]);
128
+ const series: ChartSeries[] = [];
129
+
130
+ for (const ser of serNodes) {
131
+ if (!ser || typeof ser !== "object") continue;
132
+ const s = ser as Record<string, unknown>;
133
+
134
+ // Series name
135
+ const txNode = getObj(s, "c:tx") ?? getObj(s, "tx");
136
+ const name = extractSeriesName(txNode);
137
+
138
+ // Values
139
+ const valNode = getObj(s, "c:val") ?? getObj(s, "val")
140
+ ?? getObj(s, "c:yVal") ?? getObj(s, "yVal");
141
+ const values = extractNumericValues(valNode);
142
+
143
+ // Bubble sizes
144
+ const bubbleSizeNode = getObj(s, "c:bubbleSize") ?? getObj(s, "bubbleSize");
145
+ const bubbleSizes = extractNumericValues(bubbleSizeNode);
146
+
147
+ // Color from shape properties
148
+ const spPr = getObj(s, "c:spPr") ?? getObj(s, "spPr");
149
+ const color = extractColor(spPr);
150
+
151
+ const entry: ChartSeries = { name, values, color };
152
+ if (bubbleSizes.length > 0) entry.bubbleSizes = bubbleSizes;
153
+ series.push(entry);
154
+ }
155
+
156
+ return series;
157
+ }
158
+
159
+ function extractSeriesName(txNode: unknown): string | undefined {
160
+ if (!txNode || typeof txNode !== "object") return undefined;
161
+ const tx = txNode as Record<string, unknown>;
162
+ const strRef = getObj(tx, "c:strRef") ?? getObj(tx, "strRef");
163
+ if (strRef) {
164
+ const cache = getObj(strRef as Record<string, unknown>, "c:strCache") ?? getObj(strRef as Record<string, unknown>, "strCache");
165
+ if (cache) {
166
+ const pt = ensureArray((cache as Record<string, unknown>)["c:pt"] ?? (cache as Record<string, unknown>)["pt"]);
167
+ if (pt[0] && typeof pt[0] === "object") {
168
+ const v = (pt[0] as Record<string, unknown>)["c:v"] ?? (pt[0] as Record<string, unknown>)["v"];
169
+ if (typeof v === "string") return v;
170
+ }
171
+ }
172
+ }
173
+ const v = getObj(tx, "c:v") ?? getObj(tx, "v");
174
+ if (typeof v === "string") return v;
175
+ return undefined;
176
+ }
177
+
178
+ function extractNumericValues(valNode: unknown): number[] {
179
+ if (!valNode || typeof valNode !== "object") return [];
180
+ const val = valNode as Record<string, unknown>;
181
+ const numRef = getObj(val, "c:numRef") ?? getObj(val, "numRef");
182
+ const cache = numRef
183
+ ? getObj(numRef as Record<string, unknown>, "c:numCache") ?? getObj(numRef as Record<string, unknown>, "numCache")
184
+ : getObj(val, "c:numLit") ?? getObj(val, "numLit");
185
+
186
+ if (!cache || typeof cache !== "object") return [];
187
+ const pts = ensureArray((cache as Record<string, unknown>)["c:pt"] ?? (cache as Record<string, unknown>)["pt"]);
188
+ const values: number[] = [];
189
+ for (const pt of pts) {
190
+ if (!pt || typeof pt !== "object") continue;
191
+ const v = (pt as Record<string, unknown>)["c:v"] ?? (pt as Record<string, unknown>)["v"];
192
+ const num = typeof v === "string" ? parseFloat(v) : typeof v === "number" ? v : NaN;
193
+ values.push(isNaN(num) ? 0 : num);
194
+ }
195
+ return values;
196
+ }
197
+
198
+ function extractCategories(chartNode: Record<string, unknown>): string[] | undefined {
199
+ const serNodes = ensureArray(chartNode["c:ser"] ?? chartNode["ser"]);
200
+ for (const ser of serNodes) {
201
+ if (!ser || typeof ser !== "object") continue;
202
+ const s = ser as Record<string, unknown>;
203
+ const catNode = getObj(s, "c:cat") ?? getObj(s, "cat");
204
+ if (!catNode || typeof catNode !== "object") continue;
205
+ const cat = catNode as Record<string, unknown>;
206
+ const strRef = getObj(cat, "c:strRef") ?? getObj(cat, "strRef");
207
+ if (strRef && typeof strRef === "object") {
208
+ const cache = getObj(strRef as Record<string, unknown>, "c:strCache")
209
+ ?? getObj(strRef as Record<string, unknown>, "strCache");
210
+ if (cache && typeof cache === "object") {
211
+ const pts = ensureArray((cache as Record<string, unknown>)["c:pt"] ?? (cache as Record<string, unknown>)["pt"]);
212
+ const cats: string[] = [];
213
+ for (const pt of pts) {
214
+ if (!pt || typeof pt !== "object") continue;
215
+ const v = (pt as Record<string, unknown>)["c:v"] ?? (pt as Record<string, unknown>)["v"];
216
+ cats.push(typeof v === "string" ? v : String(v ?? ""));
217
+ }
218
+ if (cats.length > 0) return cats;
219
+ }
220
+ }
221
+ }
222
+ return undefined;
223
+ }
224
+
225
+ function extractColor(spPr: unknown): string | undefined {
226
+ if (!spPr || typeof spPr !== "object") return undefined;
227
+ const obj = spPr as Record<string, unknown>;
228
+ const solidFill = getObj(obj, "a:solidFill") ?? getObj(obj, "solidFill");
229
+ if (!solidFill || typeof solidFill !== "object") return undefined;
230
+ const srgb = getObj(solidFill as Record<string, unknown>, "a:srgbClr") ?? getObj(solidFill as Record<string, unknown>, "srgbClr");
231
+ if (srgb && typeof srgb === "object") {
232
+ const val = (srgb as Record<string, unknown>)["@_val"];
233
+ if (typeof val === "string") return `#${val}`;
234
+ }
235
+ return undefined;
236
+ }
237
+
238
+ // =============================================================================
239
+ // Title
240
+ // =============================================================================
241
+
242
+ function extractTitle(titleNode: unknown): string | undefined {
243
+ if (!titleNode || typeof titleNode !== "object") return undefined;
244
+ const tx = getObj(titleNode as Record<string, unknown>, "c:tx") ?? getObj(titleNode as Record<string, unknown>, "tx");
245
+ if (!tx || typeof tx !== "object") return undefined;
246
+ const rich = getObj(tx as Record<string, unknown>, "c:rich") ?? getObj(tx as Record<string, unknown>, "rich");
247
+ if (!rich || typeof rich !== "object") return undefined;
248
+ const paras = ensureArray((rich as Record<string, unknown>)["a:p"] ?? (rich as Record<string, unknown>)["p"]);
249
+ const texts: string[] = [];
250
+ for (const para of paras) {
251
+ if (!para || typeof para !== "object") continue;
252
+ const runs = ensureArray((para as Record<string, unknown>)["a:r"] ?? (para as Record<string, unknown>)["r"]);
253
+ for (const run of runs) {
254
+ if (!run || typeof run !== "object") continue;
255
+ const t = (run as Record<string, unknown>)["a:t"] ?? (run as Record<string, unknown>)["t"];
256
+ if (typeof t === "string") texts.push(t);
257
+ }
258
+ }
259
+ return texts.length > 0 ? texts.join("") : undefined;
260
+ }
261
+
262
+ // =============================================================================
263
+ // Legend
264
+ // =============================================================================
265
+
266
+ function extractLegendPosition(legendNode: unknown): ParsedChart["legendPosition"] {
267
+ if (!legendNode || typeof legendNode !== "object") return undefined;
268
+ const pos = getObj(legendNode as Record<string, unknown>, "c:legendPos") ?? getObj(legendNode as Record<string, unknown>, "legendPos");
269
+ if (!pos || typeof pos !== "object") return undefined;
270
+ const val = (pos as Record<string, unknown>)["@_val"];
271
+ const map: Record<string, ParsedChart["legendPosition"]> = { t: "top", b: "bottom", l: "left", r: "right" };
272
+ return typeof val === "string" ? map[val] ?? "right" : "right";
273
+ }
274
+
275
+ // =============================================================================
276
+ // Axes
277
+ // =============================================================================
278
+
279
+ function parseAxes(plotArea: Record<string, unknown>): ChartAxis[] {
280
+ const axes: ChartAxis[] = [];
281
+
282
+ const catAx = ensureArray(plotArea["c:catAx"] ?? plotArea["catAx"]);
283
+ for (const ax of catAx) {
284
+ if (!ax || typeof ax !== "object") continue;
285
+ axes.push(parseAxis(ax as Record<string, unknown>, "category"));
286
+ }
287
+
288
+ const valAx = ensureArray(plotArea["c:valAx"] ?? plotArea["valAx"]);
289
+ for (const ax of valAx) {
290
+ if (!ax || typeof ax !== "object") continue;
291
+ axes.push(parseAxis(ax as Record<string, unknown>, "value"));
292
+ }
293
+
294
+ const dateAx = ensureArray(plotArea["c:dateAx"] ?? plotArea["dateAx"]);
295
+ for (const ax of dateAx) {
296
+ if (!ax || typeof ax !== "object") continue;
297
+ axes.push(parseAxis(ax as Record<string, unknown>, "date"));
298
+ }
299
+
300
+ return axes;
301
+ }
302
+
303
+ function parseAxis(ax: Record<string, unknown>, type: ChartAxis["type"]): ChartAxis {
304
+ const titleNode = getObj(ax, "c:title") ?? getObj(ax, "title");
305
+ const title = extractTitle(titleNode);
306
+
307
+ const numFmtNode = getObj(ax, "c:numFmt") ?? getObj(ax, "numFmt");
308
+ const numFmt = numFmtNode ? getAttr(numFmtNode as Record<string, unknown>, "", "formatCode") ?? (numFmtNode as Record<string, unknown>)["@_formatCode"] as string | undefined : undefined;
309
+
310
+ const scalingNode = getObj(ax, "c:scaling") ?? getObj(ax, "scaling");
311
+ let min: number | undefined;
312
+ let max: number | undefined;
313
+ if (scalingNode && typeof scalingNode === "object") {
314
+ const minNode = getObj(scalingNode as Record<string, unknown>, "c:min") ?? getObj(scalingNode as Record<string, unknown>, "min");
315
+ if (minNode && typeof minNode === "object") {
316
+ const v = (minNode as Record<string, unknown>)["@_val"];
317
+ if (typeof v === "string") min = parseFloat(v);
318
+ }
319
+ const maxNode = getObj(scalingNode as Record<string, unknown>, "c:max") ?? getObj(scalingNode as Record<string, unknown>, "max");
320
+ if (maxNode && typeof maxNode === "object") {
321
+ const v = (maxNode as Record<string, unknown>)["@_val"];
322
+ if (typeof v === "string") max = parseFloat(v);
323
+ }
324
+ }
325
+
326
+ return { type, title, numFmt, min, max };
327
+ }
328
+
329
+ // =============================================================================
330
+ // Helpers
331
+ // =============================================================================
332
+
333
+ function getObj(parent: Record<string, unknown>, key: string): unknown {
334
+ return parent[key];
335
+ }
336
+
337
+ function getAttr(obj: Record<string, unknown>, childKey: string, attr: string): string | undefined {
338
+ const child = childKey ? obj[childKey] : obj;
339
+ if (!child || typeof child !== "object") return undefined;
340
+ const val = (child as Record<string, unknown>)[`@_${attr}`];
341
+ return typeof val === "string" ? val : undefined;
342
+ }
343
+
344
+ function ensureArray(val: unknown): unknown[] {
345
+ if (val === undefined || val === null) return [];
346
+ return Array.isArray(val) ? val : [val];
347
+ }
@@ -0,0 +1,207 @@
1
+ // =============================================================================
2
+ // Chart & Drawing Types
3
+ // =============================================================================
4
+ //
5
+ // Types for parsed chart, sparkline, table, and drawing objects from OOXML.
6
+ // These are read from xl/charts/*.xml, xl/tables/*.xml, and
7
+ // xl/drawings/*.xml during workbook loading.
8
+ // =============================================================================
9
+
10
+ // =============================================================================
11
+ // Charts
12
+ // =============================================================================
13
+
14
+ export interface ParsedChart {
15
+ /** Chart title text. */
16
+ title?: string;
17
+ /** Chart type (bar, line, pie, scatter, area, etc.). */
18
+ chartType: ChartType;
19
+ /** Data series. */
20
+ series: ChartSeries[];
21
+ /** Category labels (X-axis or pie labels). */
22
+ categories?: string[];
23
+ /** Axis configuration. */
24
+ axes?: ChartAxis[];
25
+ /** Legend position. */
26
+ legendPosition?: "top" | "bottom" | "left" | "right" | "none";
27
+ /** Anchor position in the sheet (row/col for top-left and bottom-right). */
28
+ anchor: DrawingAnchor;
29
+ }
30
+
31
+ export type ChartType =
32
+ | "bar"
33
+ | "col"
34
+ | "line"
35
+ | "pie"
36
+ | "doughnut"
37
+ | "area"
38
+ | "scatter"
39
+ | "bubble"
40
+ | "radar"
41
+ | "stock"
42
+ | "surface"
43
+ | "combo"
44
+ | "unknown";
45
+
46
+ export interface ChartSeries {
47
+ /** Series name/label. */
48
+ name?: string;
49
+ /** Data values. */
50
+ values: number[];
51
+ /** Series color (resolved from theme/style). */
52
+ color?: string;
53
+ /** Bubble sizes (for bubble charts). */
54
+ bubbleSizes?: number[];
55
+ /** Per-series chart type (for combo charts). */
56
+ seriesChartType?: ChartType;
57
+ }
58
+
59
+ export interface ChartAxis {
60
+ /** Axis type. */
61
+ type: "category" | "value" | "date" | "series";
62
+ /** Axis title. */
63
+ title?: string;
64
+ /** Number format for axis labels. */
65
+ numFmt?: string;
66
+ /** Min/max bounds. */
67
+ min?: number;
68
+ max?: number;
69
+ }
70
+
71
+ // =============================================================================
72
+ // Sparklines
73
+ // =============================================================================
74
+
75
+ export interface ParsedSparklineGroup {
76
+ /** Sparkline type. */
77
+ type: "line" | "column" | "stacked";
78
+ /** Individual sparklines in the group. */
79
+ sparklines: ParsedSparkline[];
80
+ /** Series color. */
81
+ color?: string;
82
+ /** Min/max axis settings. */
83
+ minAxis?: "individual" | "group" | "custom";
84
+ maxAxis?: "individual" | "group" | "custom";
85
+ }
86
+
87
+ export interface ParsedSparkline {
88
+ /** Data range (e.g., "Sheet1!A1:A10"). */
89
+ dataRange: string;
90
+ /** Cell where the sparkline is rendered. */
91
+ locationRef: string;
92
+ }
93
+
94
+ // =============================================================================
95
+ // Structured Tables
96
+ // =============================================================================
97
+
98
+ export interface ParsedTable {
99
+ /** Table name (used in structured references like TableName[Column]). */
100
+ name: string;
101
+ /** Display name. */
102
+ displayName: string;
103
+ /** Cell range (e.g., "A1:D10"). */
104
+ ref: string;
105
+ /** Column definitions. */
106
+ columns: TableColumn[];
107
+ /** Whether the table has a header row. */
108
+ headerRow: boolean;
109
+ /** Whether the table has a totals row. */
110
+ totalsRow: boolean;
111
+ /** Table style name (e.g., "TableStyleMedium2"). */
112
+ styleName?: string;
113
+ /** Auto-filter enabled. */
114
+ autoFilter: boolean;
115
+ }
116
+
117
+ export interface TableColumn {
118
+ /** Column ID (1-based within the table). */
119
+ id: number;
120
+ /** Column header text. */
121
+ name: string;
122
+ /** Totals row function (sum, count, average, etc.). */
123
+ totalsFunction?: string;
124
+ /** Totals row formula. */
125
+ totalsFormula?: string;
126
+ }
127
+
128
+ // =============================================================================
129
+ // Drawings (shapes, text boxes, etc.)
130
+ // =============================================================================
131
+
132
+ export interface ParsedDrawing {
133
+ /** Drawing type. */
134
+ type: "shape" | "textbox" | "connector" | "group" | "unknown";
135
+ /** Shape geometry preset (rect, roundRect, ellipse, etc.). */
136
+ geometry?: string;
137
+ /** Shape text content. */
138
+ text?: string;
139
+ /** Fill color. */
140
+ fillColor?: string;
141
+ /** Outline color. */
142
+ outlineColor?: string;
143
+ /** Outline width in points. */
144
+ outlineWidth?: number;
145
+ /** Anchor position. */
146
+ anchor: DrawingAnchor;
147
+ }
148
+
149
+ // =============================================================================
150
+ // Drawing anchors — position relative to cells
151
+ // =============================================================================
152
+
153
+ export interface DrawingAnchor {
154
+ /** Top-left cell. */
155
+ from: { row: number; col: number; rowOffset?: number; colOffset?: number };
156
+ /** Bottom-right cell. */
157
+ to: { row: number; col: number; rowOffset?: number; colOffset?: number };
158
+ }
159
+
160
+ // =============================================================================
161
+ // Auto-filters
162
+ // =============================================================================
163
+
164
+ export interface AutoFilterRange {
165
+ /** Range reference (e.g., "A1:D10"). */
166
+ ref: string;
167
+ /** Per-column filter criteria. */
168
+ columns: AutoFilterColumn[];
169
+ }
170
+
171
+ export interface AutoFilterColumn {
172
+ /** Column index within the filter range (0-based). */
173
+ colIndex: number;
174
+ /** Filter values (for value-based filters). */
175
+ filterValues?: string[];
176
+ /** Custom filter condition. */
177
+ customFilter?: { operator: string; value: string };
178
+ }
179
+
180
+ // =============================================================================
181
+ // Data Validation
182
+ // =============================================================================
183
+
184
+ export interface ParsedDataValidation {
185
+ /** Cell range this validation applies to. */
186
+ ref: string;
187
+ /** Validation type. */
188
+ type: "list" | "whole" | "decimal" | "date" | "time" | "textLength" | "custom" | "none";
189
+ /** Comparison operator. */
190
+ operator?: "between" | "notBetween" | "equal" | "notEqual" | "greaterThan" | "lessThan" | "greaterThanOrEqual" | "lessThanOrEqual";
191
+ /** Formula for value 1. */
192
+ formula1?: string;
193
+ /** Formula for value 2 (for between/notBetween). */
194
+ formula2?: string;
195
+ /** Whether to show dropdown for list validation. */
196
+ showDropdown: boolean;
197
+ /** Error alert style. */
198
+ errorStyle?: "stop" | "warning" | "information";
199
+ /** Error alert title. */
200
+ errorTitle?: string;
201
+ /** Error alert message. */
202
+ errorMessage?: string;
203
+ /** Input prompt title. */
204
+ promptTitle?: string;
205
+ /** Input prompt message. */
206
+ promptMessage?: string;
207
+ }