@origints/xlsx 0.1.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/parse.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { XlsxWorkbook } from './xlsx-workbook';
2
+ import { XlsxSpecBuilder } from './xlsx-spec-builder';
2
3
  /**
3
4
  * Transform AST specification.
4
5
  * Compatible with @origints/core TransformAst.
@@ -9,6 +10,14 @@ export interface TransformAst {
9
10
  readonly name: string;
10
11
  readonly args?: unknown;
11
12
  }
13
+ /**
14
+ * Typed transform AST with spec builder type parameter.
15
+ * Compatible with @origints/core TypedTransformAst.
16
+ */
17
+ export interface TypedTransformAst<SB = unknown> extends TransformAst {
18
+ readonly __specBuilder?: SB;
19
+ readonly specBuilderFactory: () => SB;
20
+ }
12
21
  /**
13
22
  * Transform implementation.
14
23
  * Compatible with @origints/core TransformImpl.
@@ -59,12 +68,12 @@ export interface XlsxParseOptions {
59
68
  * .in(loadFile('data.xlsx'))
60
69
  * .mapIn(parseXlsx())
61
70
  * .emit((out, $) => out
62
- * .add('revenue', $.sheet('Sales').cell('B2').number())
71
+ * .add('revenue', $.get('Sales').get('B2').number())
63
72
  * )
64
73
  * .compile()
65
74
  * ```
66
75
  */
67
- export declare function parseXlsx(options?: XlsxParseOptions): TransformAst;
76
+ export declare function parseXlsx(options?: XlsxParseOptions): TypedTransformAst<XlsxSpecBuilder>;
68
77
  /**
69
78
  * Sync transform implementation for parseXlsx.
70
79
  * Note: XLSX parsing requires async due to file reading.
@@ -1,5 +1,78 @@
1
- import { Cell as ExcelJSCell } from 'exceljs';
1
+ import { TYPE_LABEL } from '@origints/core';
2
2
  import { XlsxResult, XlsxPath, CellValue, ExtendedCellValue } from './xlsx-result';
3
+ /**
4
+ * Internal cell data — parser-agnostic representation.
5
+ * Created by the parser (SheetJS) and consumed by XlsxCell.
6
+ */
7
+ export interface CellData {
8
+ readonly address: string;
9
+ readonly row: number;
10
+ readonly col: number;
11
+ /** Resolved value (formula result or plain value). */
12
+ readonly resolvedValue: CellValue;
13
+ /** Formula string, if this cell contains a formula. */
14
+ readonly formula?: string;
15
+ /** Error string, if this cell contains an error. */
16
+ readonly error?: string;
17
+ /** Hyperlink URL. */
18
+ readonly hyperlink?: string;
19
+ /** Comment/note text. */
20
+ readonly comment?: string;
21
+ /** Number format string. */
22
+ readonly numFmt?: string;
23
+ /** Whether this cell is part of a merged range. */
24
+ readonly isMerged: boolean;
25
+ /** Address of the master cell if this is a non-master merged cell. */
26
+ readonly masterAddress?: string;
27
+ /** Rich text segments (when available from the parser). */
28
+ readonly richText?: ReadonlyArray<RichTextSegmentData>;
29
+ /** Cell style data (best-effort, may not be available with all parsers). */
30
+ readonly style?: CellStyleData;
31
+ }
32
+ /** Rich text segment from the parser. */
33
+ export interface RichTextSegmentData {
34
+ readonly text: string;
35
+ readonly bold?: boolean;
36
+ readonly italic?: boolean;
37
+ readonly underline?: boolean;
38
+ readonly strike?: boolean;
39
+ readonly color?: string;
40
+ readonly size?: number;
41
+ readonly font?: string;
42
+ }
43
+ /** Cell style from the parser. */
44
+ export interface CellStyleData {
45
+ readonly font?: {
46
+ readonly name?: string;
47
+ readonly size?: number;
48
+ readonly bold?: boolean;
49
+ readonly italic?: boolean;
50
+ readonly underline?: boolean;
51
+ readonly strike?: boolean;
52
+ readonly color?: {
53
+ readonly argb?: string;
54
+ readonly theme?: number;
55
+ };
56
+ };
57
+ readonly fill?: {
58
+ readonly type?: string;
59
+ readonly pattern?: string;
60
+ readonly fgColor?: {
61
+ readonly argb?: string;
62
+ };
63
+ };
64
+ readonly border?: {
65
+ readonly top?: boolean;
66
+ readonly bottom?: boolean;
67
+ readonly left?: boolean;
68
+ readonly right?: boolean;
69
+ };
70
+ readonly alignment?: {
71
+ readonly horizontal?: string;
72
+ readonly vertical?: string;
73
+ readonly wrapText?: boolean;
74
+ };
75
+ }
3
76
  /**
4
77
  * Cell style information.
5
78
  */
@@ -51,22 +124,28 @@ export interface RichTextContent {
51
124
  readonly hyperlink?: string;
52
125
  }
53
126
  /**
54
- * A wrapper around ExcelJS Cell with full metadata preservation.
127
+ * A wrapper around cell data with full metadata preservation.
55
128
  *
56
129
  * XlsxCell enables typed extraction of cell values while maintaining
57
130
  * provenance information. Each cell knows its exact location in the
58
131
  * workbook, allowing full traceability.
59
132
  */
60
133
  export declare class XlsxCell {
61
- private readonly cell;
134
+ private readonly _data;
62
135
  private readonly _path;
63
136
  private readonly _sheetName;
137
+ get [TYPE_LABEL](): string;
64
138
  private constructor();
65
139
  /**
66
- * Creates an XlsxCell from an ExcelJS cell.
140
+ * Creates an XlsxCell from internal cell data.
67
141
  * @internal
68
142
  */
69
- static fromExcelJS(cell: ExcelJSCell, sheetName: string, file?: string): XlsxCell;
143
+ static create(data: CellData, sheetName: string, file?: string): XlsxCell;
144
+ /**
145
+ * Creates an empty cell for the given coordinates.
146
+ * @internal
147
+ */
148
+ static empty(row: number, col: number, sheetName: string, file?: string): XlsxCell;
70
149
  /**
71
150
  * Returns the cell address (e.g., "A1").
72
151
  */
@@ -121,18 +200,10 @@ export declare class XlsxCell {
121
200
  formula(): XlsxResult<string>;
122
201
  /**
123
202
  * Get the cell content as rich text with formatting preserved.
124
- * Returns segments with their individual formatting properties.
125
203
  */
126
204
  richText(): XlsxResult<RichTextContent>;
127
205
  /**
128
206
  * Get the cell content as Markdown.
129
- *
130
- * Converts cell content including:
131
- * - Bold text -> **text**
132
- * - Italic text -> *text*
133
- * - Strikethrough -> ~~text~~
134
- * - Hyperlinks -> [text](url)
135
- * - Rich text segments with mixed formatting
136
207
  */
137
208
  markdown(): XlsxResult<string>;
138
209
  /**
@@ -193,17 +264,13 @@ export declare class XlsxCell {
193
264
  */
194
265
  getOffsetCell(rowDelta: number, colDelta: number, getCell: (row: number, col: number) => XlsxCell | undefined): XlsxResult<XlsxCell>;
195
266
  /**
196
- * Get the raw value, handling ExcelJS value types.
267
+ * Get the raw value, handling formula/error types.
197
268
  */
198
269
  private getRawValue;
199
270
  /**
200
271
  * Get the resolved value (formula result or plain value).
201
272
  */
202
273
  private getResolvedValue;
203
- /**
204
- * Normalize a formula result value.
205
- */
206
- private normalizeResult;
207
274
  /**
208
275
  * Convert Excel date number to JavaScript Date.
209
276
  * Excel dates are days since 1900-01-01 (with a leap year bug).
@@ -1,3 +1,4 @@
1
+ import { TYPE_LABEL } from '@origints/core';
1
2
  import { XlsxSheet } from './xlsx-sheet';
2
3
  import { XlsxCell } from './xlsx-cell';
3
4
  import { XlsxResult, XlsxPath } from './xlsx-result';
@@ -33,6 +34,7 @@ export declare class XlsxCursor {
33
34
  private readonly _row;
34
35
  private readonly _col;
35
36
  private readonly _direction;
37
+ get [TYPE_LABEL](): string;
36
38
  private constructor();
37
39
  /**
38
40
  * Create a cursor from an address string.
@@ -0,0 +1,24 @@
1
+ import { SpecScope } from '@origints/core';
2
+ import { XlsxSheet } from './xlsx-sheet';
3
+ import { CellPredicate } from './xlsx-query';
4
+ import { SheetPredicate } from './xlsx-workbook';
5
+ import { CellPredicateSpec, SheetPredicateSpec, RowPredicateSpec } from './xlsx-predicate-spec';
6
+ /**
7
+ * Compile a CellPredicateSpec AST into a runtime CellPredicate closure.
8
+ *
9
+ * @param spec - The predicate spec to compile
10
+ * @param scope - Optional scope for variable reference predicates (equalsRef, containsRef)
11
+ */
12
+ export declare function compileCellPredicate(spec: CellPredicateSpec, scope?: SpecScope): CellPredicate;
13
+ /**
14
+ * Compile a SheetPredicateSpec AST into a runtime SheetPredicate closure.
15
+ */
16
+ export declare function compileSheetPredicate(spec: SheetPredicateSpec): SheetPredicate;
17
+ export type RowPredicate = (sheet: XlsxSheet, row: number, anchorCol: number) => boolean;
18
+ /**
19
+ * Compile a RowPredicateSpec AST into a runtime RowPredicate closure.
20
+ *
21
+ * @param spec - The row predicate spec to compile
22
+ * @param scope - Optional scope for variable reference predicates
23
+ */
24
+ export declare function compileRowPredicate(spec: RowPredicateSpec, scope?: SpecScope): RowPredicate;
@@ -0,0 +1,211 @@
1
+ import { PredicateWrapper } from '@origints/core';
2
+ import { CellValue } from './xlsx-result';
3
+ export type CellPredicateSpec = {
4
+ readonly kind: 'equals';
5
+ readonly value: CellValue;
6
+ } | {
7
+ readonly kind: 'contains';
8
+ readonly text: string;
9
+ readonly caseSensitive: boolean;
10
+ } | {
11
+ readonly kind: 'matches';
12
+ readonly pattern: string;
13
+ readonly flags: string;
14
+ } | {
15
+ readonly kind: 'startsWith';
16
+ readonly prefix: string;
17
+ readonly caseSensitive: boolean;
18
+ } | {
19
+ readonly kind: 'endsWith';
20
+ readonly suffix: string;
21
+ readonly caseSensitive: boolean;
22
+ } | {
23
+ readonly kind: 'isString';
24
+ } | {
25
+ readonly kind: 'isNumber';
26
+ } | {
27
+ readonly kind: 'isBoolean';
28
+ } | {
29
+ readonly kind: 'isDate';
30
+ } | {
31
+ readonly kind: 'isEmpty';
32
+ } | {
33
+ readonly kind: 'isNotEmpty';
34
+ } | {
35
+ readonly kind: 'isFormula';
36
+ } | {
37
+ readonly kind: 'isError';
38
+ } | {
39
+ readonly kind: 'isMerged';
40
+ } | {
41
+ readonly kind: 'gt';
42
+ readonly value: number;
43
+ } | {
44
+ readonly kind: 'gte';
45
+ readonly value: number;
46
+ } | {
47
+ readonly kind: 'lt';
48
+ readonly value: number;
49
+ } | {
50
+ readonly kind: 'lte';
51
+ readonly value: number;
52
+ } | {
53
+ readonly kind: 'between';
54
+ readonly min: number;
55
+ readonly max: number;
56
+ } | {
57
+ readonly kind: 'isBold';
58
+ } | {
59
+ readonly kind: 'isItalic';
60
+ } | {
61
+ readonly kind: 'hasHyperlink';
62
+ } | {
63
+ readonly kind: 'hasComment';
64
+ } | {
65
+ readonly kind: 'equalsRef';
66
+ readonly ref: string;
67
+ readonly path?: readonly (string | number)[];
68
+ } | {
69
+ readonly kind: 'containsRef';
70
+ readonly ref: string;
71
+ readonly path?: readonly (string | number)[];
72
+ readonly caseSensitive: boolean;
73
+ } | {
74
+ readonly kind: 'startsWithRef';
75
+ readonly ref: string;
76
+ readonly path?: readonly (string | number)[];
77
+ readonly caseSensitive: boolean;
78
+ } | {
79
+ readonly kind: 'and';
80
+ readonly left: CellPredicateSpec;
81
+ readonly right: CellPredicateSpec;
82
+ } | {
83
+ readonly kind: 'or';
84
+ readonly left: CellPredicateSpec;
85
+ readonly right: CellPredicateSpec;
86
+ } | {
87
+ readonly kind: 'not';
88
+ readonly operand: CellPredicateSpec;
89
+ };
90
+ export type SheetPredicateSpec = {
91
+ readonly kind: 'nameEquals';
92
+ readonly name: string;
93
+ } | {
94
+ readonly kind: 'nameContains';
95
+ readonly text: string;
96
+ } | {
97
+ readonly kind: 'nameStartsWith';
98
+ readonly prefix: string;
99
+ } | {
100
+ readonly kind: 'nameEndsWith';
101
+ readonly suffix: string;
102
+ } | {
103
+ readonly kind: 'nameMatches';
104
+ readonly pattern: string;
105
+ readonly flags: string;
106
+ } | {
107
+ readonly kind: 'and';
108
+ readonly left: SheetPredicateSpec;
109
+ readonly right: SheetPredicateSpec;
110
+ } | {
111
+ readonly kind: 'or';
112
+ readonly left: SheetPredicateSpec;
113
+ readonly right: SheetPredicateSpec;
114
+ } | {
115
+ readonly kind: 'not';
116
+ readonly operand: SheetPredicateSpec;
117
+ };
118
+ export type CellPred = PredicateWrapper<CellPredicateSpec>;
119
+ export type SheetPred = PredicateWrapper<SheetPredicateSpec>;
120
+ export declare const cell: Readonly<{
121
+ equals: (value: CellValue) => CellPred;
122
+ contains: (text: string, caseSensitive?: boolean) => CellPred;
123
+ matches: (pattern: RegExp) => CellPred;
124
+ startsWith: (prefix: string, caseSensitive?: boolean) => CellPred;
125
+ endsWith: (suffix: string, caseSensitive?: boolean) => CellPred;
126
+ isString: () => CellPred;
127
+ isNumber: () => CellPred;
128
+ isBoolean: () => CellPred;
129
+ isDate: () => CellPred;
130
+ isEmpty: () => CellPred;
131
+ isNotEmpty: () => CellPred;
132
+ isFormula: () => CellPred;
133
+ isError: () => CellPred;
134
+ isMerged: () => CellPred;
135
+ gt: (value: number) => CellPred;
136
+ gte: (value: number) => CellPred;
137
+ lt: (value: number) => CellPred;
138
+ lte: (value: number) => CellPred;
139
+ between: (min: number, max: number) => CellPred;
140
+ isBold: () => CellPred;
141
+ isItalic: () => CellPred;
142
+ hasHyperlink: () => CellPred;
143
+ hasComment: () => CellPred;
144
+ /**
145
+ * Match when cell value equals a bound variable from ForEachSpec scope.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * forEach(
150
+ * $.get('companies').strings(),
151
+ * 'company',
152
+ * xlsxSB.firstSheet().find(cell.equalsRef('company')).string()
153
+ * )
154
+ * ```
155
+ */
156
+ equalsRef: (ref: string, path?: readonly (string | number)[]) => CellPred;
157
+ /**
158
+ * Match when cell string value contains a bound variable from ForEachSpec scope.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * forEach(
163
+ * $.get('keywords').strings(),
164
+ * 'keyword',
165
+ * xlsxSB.firstSheet().find(cell.containsRef('keyword')).string()
166
+ * )
167
+ * ```
168
+ */
169
+ containsRef: (ref: string, path?: readonly (string | number)[], caseSensitive?: boolean) => CellPred;
170
+ /**
171
+ * Match when cell string value starts with a bound variable from ForEachSpec scope.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * forEach(
176
+ * $.get('companies').strings(),
177
+ * 'company',
178
+ * xlsxSB.firstSheet().find(cell.startsWithRef('company')).string()
179
+ * )
180
+ * ```
181
+ */
182
+ startsWithRef: (ref: string, path?: readonly (string | number)[], caseSensitive?: boolean) => CellPred;
183
+ }>;
184
+ export declare const sheet: Readonly<{
185
+ nameEquals: (name: string) => SheetPred;
186
+ nameContains: (text: string) => SheetPred;
187
+ nameStartsWith: (prefix: string) => SheetPred;
188
+ nameEndsWith: (suffix: string) => SheetPred;
189
+ nameMatches: (pattern: RegExp) => SheetPred;
190
+ }>;
191
+ export declare function describeCellPredicate(spec: CellPredicateSpec): string;
192
+ export type RowPredicateSpec = {
193
+ readonly kind: 'rowCol';
194
+ readonly offset: number;
195
+ readonly predicate: CellPredicateSpec;
196
+ } | {
197
+ readonly kind: 'and';
198
+ readonly left: RowPredicateSpec;
199
+ readonly right: RowPredicateSpec;
200
+ } | {
201
+ readonly kind: 'or';
202
+ readonly left: RowPredicateSpec;
203
+ readonly right: RowPredicateSpec;
204
+ } | {
205
+ readonly kind: 'not';
206
+ readonly operand: RowPredicateSpec;
207
+ };
208
+ export type RowPred = PredicateWrapper<RowPredicateSpec>;
209
+ export declare function rowCol(offset: number, predicate: CellPred): RowPred;
210
+ export declare function describeRowPredicate(spec: RowPredicateSpec): string;
211
+ export declare function describeSheetPredicate(spec: SheetPredicateSpec): string;
@@ -1,7 +1,12 @@
1
- import { Worksheet } from 'exceljs';
1
+ import { TYPE_LABEL } from '@origints/core';
2
2
  import { XlsxCell } from './xlsx-cell';
3
3
  import { XlsxResult, XlsxPath, CellValue } from './xlsx-result';
4
4
  import { CellPredicate } from './xlsx-query';
5
+ /**
6
+ * Cell accessor function — resolves a cell by 1-based row/col.
7
+ * Provided by XlsxSheet so XlsxRange is decoupled from sheet internals.
8
+ */
9
+ export type CellAccessor = (row: number, col: number) => XlsxResult<XlsxCell>;
5
10
  /**
6
11
  * A range of cells in an XLSX sheet.
7
12
  *
@@ -10,7 +15,7 @@ import { CellPredicate } from './xlsx-query';
10
15
  * and conversion to various formats.
11
16
  */
12
17
  export declare class XlsxRange {
13
- private readonly worksheet;
18
+ private readonly _getCell;
14
19
  private readonly _startRow;
15
20
  private readonly _startCol;
16
21
  private readonly _endRow;
@@ -18,17 +23,18 @@ export declare class XlsxRange {
18
23
  private readonly _path;
19
24
  private readonly _sheetName;
20
25
  private readonly _file?;
26
+ get [TYPE_LABEL](): string;
21
27
  private constructor();
22
28
  /**
23
29
  * Creates an XlsxRange from coordinates.
24
30
  * @internal
25
31
  */
26
- static fromCoords(worksheet: Worksheet, startRow: number, startCol: number, endRow: number, endCol: number, sheetName: string, file?: string): XlsxRange;
32
+ static fromCoords(getCell: CellAccessor, startRow: number, startCol: number, endRow: number, endCol: number, sheetName: string, file?: string): XlsxRange;
27
33
  /**
28
34
  * Creates an XlsxRange from an address string.
29
35
  * @internal
30
36
  */
31
- static fromAddress(worksheet: Worksheet, address: string, sheetName: string, file?: string): XlsxResult<XlsxRange>;
37
+ static fromAddress(getCell: CellAccessor, address: string, sheetName: string, file?: string): XlsxResult<XlsxRange>;
32
38
  /**
33
39
  * Returns the range address (e.g., "A1:C10").
34
40
  */
@@ -1,9 +1,9 @@
1
- import { Worksheet } from 'exceljs';
2
- import { XlsxCell } from './xlsx-cell';
1
+ import { TYPE_LABEL } from '@origints/core';
2
+ import { CellData, XlsxCell } from './xlsx-cell';
3
3
  import { XlsxRange } from './xlsx-range';
4
- import { XlsxCursor } from './xlsx-cursor';
5
- import { XlsxResult, XlsxPath, CellValue } from './xlsx-result';
4
+ import { XlsxResult, XlsxPath } from './xlsx-result';
6
5
  import { CellPredicate } from './xlsx-query';
6
+ import { XlsxCursor } from './xlsx-cursor';
7
7
  /**
8
8
  * Dimensions of a sheet's used range.
9
9
  */
@@ -16,22 +16,30 @@ export interface SheetDimensions {
16
16
  readonly colCount: number;
17
17
  }
18
18
  /**
19
- * A wrapper around ExcelJS Worksheet with navigation and query capabilities.
19
+ * A wrapper around sheet data with navigation and query capabilities.
20
20
  *
21
21
  * XlsxSheet provides a rich API for navigating cells, ranges, and rows
22
22
  * within a worksheet. It supports predicate-based queries and cursor-based
23
23
  * navigation for complex data extraction scenarios.
24
24
  */
25
25
  export declare class XlsxSheet {
26
- private readonly worksheet;
26
+ get [TYPE_LABEL](): string;
27
+ /** Cell data from the parser — keyed by row*16384+col. */
28
+ private readonly _cells;
29
+ /** Cache for XlsxCell wrappers — avoids repeated creation. */
30
+ private readonly _cellCache;
31
+ private readonly _dims;
32
+ private readonly _name;
33
+ private readonly _index;
34
+ private readonly _mergeRanges;
27
35
  private readonly _path;
28
36
  private readonly _file?;
29
37
  private constructor();
30
38
  /**
31
- * Creates an XlsxSheet from an ExcelJS worksheet.
39
+ * Creates an XlsxSheet from internal data.
32
40
  * @internal
33
41
  */
34
- static fromExcelJS(worksheet: Worksheet, file?: string): XlsxSheet;
42
+ static create(cells: Map<number, CellData>, dims: SheetDimensions, name: string, index: number, mergeRanges: string[], file?: string): XlsxSheet;
35
43
  /**
36
44
  * Returns the sheet name.
37
45
  */
@@ -55,7 +63,7 @@ export declare class XlsxSheet {
55
63
  /**
56
64
  * Get the value of a cell directly.
57
65
  */
58
- getValue(address: string): XlsxResult<CellValue>;
66
+ getValue(address: string): XlsxResult<import('./xlsx-result').CellValue>;
59
67
  /**
60
68
  * Get a range by its address (e.g., "A1:C10").
61
69
  */
@@ -107,7 +115,7 @@ export declare class XlsxSheet {
107
115
  /**
108
116
  * Find the first cell with the specified value.
109
117
  */
110
- findValue(value: CellValue): XlsxResult<XlsxCell>;
118
+ findValue(value: import('./xlsx-result').CellValue): XlsxResult<XlsxCell>;
111
119
  /**
112
120
  * Find cells within a specific range matching the predicate.
113
121
  */
@@ -136,11 +144,6 @@ export declare class XlsxSheet {
136
144
  * Check if a cell is part of a merged range.
137
145
  */
138
146
  isMerged(address: string): boolean;
139
- /**
140
- * Get the underlying ExcelJS worksheet.
141
- * @internal
142
- */
143
- getWorksheet(): Worksheet;
144
147
  /**
145
148
  * Get a cell by coordinates for internal use.
146
149
  * @internal