@origints/xlsx 0.2.0 → 0.4.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.
@@ -1,6 +1,78 @@
1
1
  import { TYPE_LABEL } from '@origints/core';
2
- import { Cell as ExcelJSCell } from 'exceljs';
3
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
+ }
4
76
  /**
5
77
  * Cell style information.
6
78
  */
@@ -52,23 +124,28 @@ export interface RichTextContent {
52
124
  readonly hyperlink?: string;
53
125
  }
54
126
  /**
55
- * A wrapper around ExcelJS Cell with full metadata preservation.
127
+ * A wrapper around cell data with full metadata preservation.
56
128
  *
57
129
  * XlsxCell enables typed extraction of cell values while maintaining
58
130
  * provenance information. Each cell knows its exact location in the
59
131
  * workbook, allowing full traceability.
60
132
  */
61
133
  export declare class XlsxCell {
62
- private readonly cell;
134
+ private readonly _data;
63
135
  private readonly _path;
64
136
  private readonly _sheetName;
65
137
  get [TYPE_LABEL](): string;
66
138
  private constructor();
67
139
  /**
68
- * Creates an XlsxCell from an ExcelJS cell.
140
+ * Creates an XlsxCell from internal cell data.
69
141
  * @internal
70
142
  */
71
- 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;
72
149
  /**
73
150
  * Returns the cell address (e.g., "A1").
74
151
  */
@@ -123,18 +200,10 @@ export declare class XlsxCell {
123
200
  formula(): XlsxResult<string>;
124
201
  /**
125
202
  * Get the cell content as rich text with formatting preserved.
126
- * Returns segments with their individual formatting properties.
127
203
  */
128
204
  richText(): XlsxResult<RichTextContent>;
129
205
  /**
130
206
  * Get the cell content as Markdown.
131
- *
132
- * Converts cell content including:
133
- * - Bold text -> **text**
134
- * - Italic text -> *text*
135
- * - Strikethrough -> ~~text~~
136
- * - Hyperlinks -> [text](url)
137
- * - Rich text segments with mixed formatting
138
207
  */
139
208
  markdown(): XlsxResult<string>;
140
209
  /**
@@ -195,17 +264,13 @@ export declare class XlsxCell {
195
264
  */
196
265
  getOffsetCell(rowDelta: number, colDelta: number, getCell: (row: number, col: number) => XlsxCell | undefined): XlsxResult<XlsxCell>;
197
266
  /**
198
- * Get the raw value, handling ExcelJS value types.
267
+ * Get the raw value, handling formula/error types.
199
268
  */
200
269
  private getRawValue;
201
270
  /**
202
271
  * Get the resolved value (formula result or plain value).
203
272
  */
204
273
  private getResolvedValue;
205
- /**
206
- * Normalize a formula result value.
207
- */
208
- private normalizeResult;
209
274
  /**
210
275
  * Convert Excel date number to JavaScript Date.
211
276
  * Excel dates are days since 1900-01-01 (with a leap year bug).
@@ -1,11 +1,15 @@
1
+ import { SpecScope } from '@origints/core';
1
2
  import { XlsxSheet } from './xlsx-sheet';
2
3
  import { CellPredicate } from './xlsx-query';
3
4
  import { SheetPredicate } from './xlsx-workbook';
4
5
  import { CellPredicateSpec, SheetPredicateSpec, RowPredicateSpec } from './xlsx-predicate-spec';
5
6
  /**
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)
7
11
  */
8
- export declare function compileCellPredicate(spec: CellPredicateSpec): CellPredicate;
12
+ export declare function compileCellPredicate(spec: CellPredicateSpec, scope?: SpecScope): CellPredicate;
9
13
  /**
10
14
  * Compile a SheetPredicateSpec AST into a runtime SheetPredicate closure.
11
15
  */
@@ -13,5 +17,8 @@ export declare function compileSheetPredicate(spec: SheetPredicateSpec): SheetPr
13
17
  export type RowPredicate = (sheet: XlsxSheet, row: number, anchorCol: number) => boolean;
14
18
  /**
15
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
16
23
  */
17
- export declare function compileRowPredicate(spec: RowPredicateSpec): RowPredicate;
24
+ export declare function compileRowPredicate(spec: RowPredicateSpec, scope?: SpecScope): RowPredicate;
@@ -1,3 +1,4 @@
1
+ import { PredicateWrapper } from '@origints/core';
1
2
  import { CellValue } from './xlsx-result';
2
3
  export type CellPredicateSpec = {
3
4
  readonly kind: 'equals';
@@ -60,6 +61,20 @@ export type CellPredicateSpec = {
60
61
  readonly kind: 'hasHyperlink';
61
62
  } | {
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;
63
78
  } | {
64
79
  readonly kind: 'and';
65
80
  readonly left: CellPredicateSpec;
@@ -100,20 +115,8 @@ export type SheetPredicateSpec = {
100
115
  readonly kind: 'not';
101
116
  readonly operand: SheetPredicateSpec;
102
117
  };
103
- export interface CellPred {
104
- readonly _spec: CellPredicateSpec;
105
- and(other: CellPred): CellPred;
106
- or(other: CellPred): CellPred;
107
- not(): CellPred;
108
- describe(): string;
109
- }
110
- export interface SheetPred {
111
- readonly _spec: SheetPredicateSpec;
112
- and(other: SheetPred): SheetPred;
113
- or(other: SheetPred): SheetPred;
114
- not(): SheetPred;
115
- describe(): string;
116
- }
118
+ export type CellPred = PredicateWrapper<CellPredicateSpec>;
119
+ export type SheetPred = PredicateWrapper<SheetPredicateSpec>;
117
120
  export declare const cell: Readonly<{
118
121
  equals: (value: CellValue) => CellPred;
119
122
  contains: (text: string, caseSensitive?: boolean) => CellPred;
@@ -138,6 +141,45 @@ export declare const cell: Readonly<{
138
141
  isItalic: () => CellPred;
139
142
  hasHyperlink: () => CellPred;
140
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;
141
183
  }>;
142
184
  export declare const sheet: Readonly<{
143
185
  nameEquals: (name: string) => SheetPred;
@@ -163,13 +205,7 @@ export type RowPredicateSpec = {
163
205
  readonly kind: 'not';
164
206
  readonly operand: RowPredicateSpec;
165
207
  };
166
- export interface RowPred {
167
- readonly _spec: RowPredicateSpec;
168
- and(other: RowPred): RowPred;
169
- or(other: RowPred): RowPred;
170
- not(): RowPred;
171
- describe(): string;
172
- }
208
+ export type RowPred = PredicateWrapper<RowPredicateSpec>;
173
209
  export declare function rowCol(offset: number, predicate: CellPred): RowPred;
174
210
  export declare function describeRowPredicate(spec: RowPredicateSpec): string;
175
211
  export declare function describeSheetPredicate(spec: SheetPredicateSpec): string;
@@ -1,8 +1,12 @@
1
1
  import { TYPE_LABEL } from '@origints/core';
2
- import { Worksheet } from 'exceljs';
3
2
  import { XlsxCell } from './xlsx-cell';
4
3
  import { XlsxResult, XlsxPath, CellValue } from './xlsx-result';
5
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>;
6
10
  /**
7
11
  * A range of cells in an XLSX sheet.
8
12
  *
@@ -11,7 +15,7 @@ import { CellPredicate } from './xlsx-query';
11
15
  * and conversion to various formats.
12
16
  */
13
17
  export declare class XlsxRange {
14
- private readonly worksheet;
18
+ private readonly _getCell;
15
19
  private readonly _startRow;
16
20
  private readonly _startCol;
17
21
  private readonly _endRow;
@@ -25,12 +29,12 @@ export declare class XlsxRange {
25
29
  * Creates an XlsxRange from coordinates.
26
30
  * @internal
27
31
  */
28
- 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;
29
33
  /**
30
34
  * Creates an XlsxRange from an address string.
31
35
  * @internal
32
36
  */
33
- 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>;
34
38
  /**
35
39
  * Returns the range address (e.g., "A1:C10").
36
40
  */
@@ -1,10 +1,9 @@
1
1
  import { TYPE_LABEL } from '@origints/core';
2
- import { Worksheet } from 'exceljs';
3
- import { XlsxCell } from './xlsx-cell';
2
+ import { CellData, XlsxCell } from './xlsx-cell';
4
3
  import { XlsxRange } from './xlsx-range';
5
- import { XlsxCursor } from './xlsx-cursor';
6
- import { XlsxResult, XlsxPath, CellValue } from './xlsx-result';
4
+ import { XlsxResult, XlsxPath } from './xlsx-result';
7
5
  import { CellPredicate } from './xlsx-query';
6
+ import { XlsxCursor } from './xlsx-cursor';
8
7
  /**
9
8
  * Dimensions of a sheet's used range.
10
9
  */
@@ -17,23 +16,30 @@ export interface SheetDimensions {
17
16
  readonly colCount: number;
18
17
  }
19
18
  /**
20
- * A wrapper around ExcelJS Worksheet with navigation and query capabilities.
19
+ * A wrapper around sheet data with navigation and query capabilities.
21
20
  *
22
21
  * XlsxSheet provides a rich API for navigating cells, ranges, and rows
23
22
  * within a worksheet. It supports predicate-based queries and cursor-based
24
23
  * navigation for complex data extraction scenarios.
25
24
  */
26
25
  export declare class XlsxSheet {
27
- 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;
28
35
  private readonly _path;
29
36
  private readonly _file?;
30
- get [TYPE_LABEL](): string;
31
37
  private constructor();
32
38
  /**
33
- * Creates an XlsxSheet from an ExcelJS worksheet.
39
+ * Creates an XlsxSheet from internal data.
34
40
  * @internal
35
41
  */
36
- 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;
37
43
  /**
38
44
  * Returns the sheet name.
39
45
  */
@@ -57,7 +63,7 @@ export declare class XlsxSheet {
57
63
  /**
58
64
  * Get the value of a cell directly.
59
65
  */
60
- getValue(address: string): XlsxResult<CellValue>;
66
+ getValue(address: string): XlsxResult<import('./xlsx-result').CellValue>;
61
67
  /**
62
68
  * Get a range by its address (e.g., "A1:C10").
63
69
  */
@@ -109,7 +115,7 @@ export declare class XlsxSheet {
109
115
  /**
110
116
  * Find the first cell with the specified value.
111
117
  */
112
- findValue(value: CellValue): XlsxResult<XlsxCell>;
118
+ findValue(value: import('./xlsx-result').CellValue): XlsxResult<XlsxCell>;
113
119
  /**
114
120
  * Find cells within a specific range matching the predicate.
115
121
  */
@@ -138,11 +144,6 @@ export declare class XlsxSheet {
138
144
  * Check if a cell is part of a merged range.
139
145
  */
140
146
  isMerged(address: string): boolean;
141
- /**
142
- * Get the underlying ExcelJS worksheet.
143
- * @internal
144
- */
145
- getWorksheet(): Worksheet;
146
147
  /**
147
148
  * Get a cell by coordinates for internal use.
148
149
  * @internal
@@ -1,11 +1,29 @@
1
- import { ArraySpec, ExtractSpec, Spec } from '@origints/core';
2
- import { XlsxStep, Direction } from './xlsx-spec';
1
+ import { StepBuilder, ArraySpec, ObjectSpec, ExtractSpec, Spec, SpecLike, VariableRefSpec } from '@origints/core';
2
+ import { XlsxStep, Direction, FindAllExtract } from './xlsx-spec';
3
3
  import { CellPred, SheetPred, RowPred } from './xlsx-predicate-spec';
4
+ /**
5
+ * Options for eachSlice iteration.
6
+ */
7
+ export interface EachSliceOptions {
8
+ /** Number of columns/rows per group (default: 1). When > 1, each slice anchors at every Nth position. */
9
+ readonly groupSize?: number;
10
+ }
11
+ /**
12
+ * Constraints for findAll to narrow the search area.
13
+ */
14
+ export interface FindAllConstraints {
15
+ readonly inRow?: number;
16
+ readonly inCol?: number;
17
+ readonly startRow?: number;
18
+ readonly endRow?: number;
19
+ readonly startCol?: number;
20
+ readonly endCol?: number;
21
+ }
4
22
  /**
5
23
  * Spec builder at the cell level — terminal extraction and navigation methods.
6
24
  */
7
- export declare class XlsxCellSB {
8
- private readonly steps;
25
+ export declare class XlsxCellSB extends StepBuilder<XlsxStep, 'xlsx'> {
26
+ protected get format(): "xlsx";
9
27
  /** @internal */
10
28
  constructor(steps: readonly XlsxStep[]);
11
29
  /** @internal Expose steps for colWhere/rowWhere headerRef */
@@ -22,6 +40,16 @@ export declare class XlsxCellSB {
22
40
  value(): ExtractSpec<XlsxStep, 'value'>;
23
41
  /** Extract cell formula */
24
42
  formula(): ExtractSpec<XlsxStep, 'formula'>;
43
+ /** Extract cell value as string, returning default on failure */
44
+ optionalString(defaultValue?: unknown): ExtractSpec<XlsxStep, 'string'>;
45
+ /** Extract cell value as number, returning default on failure */
46
+ optionalNumber(defaultValue?: unknown): ExtractSpec<XlsxStep, 'number'>;
47
+ /** Extract cell value as boolean, returning default on failure */
48
+ optionalBoolean(defaultValue?: unknown): ExtractSpec<XlsxStep, 'boolean'>;
49
+ /** Extract cell value as Date, returning default on failure */
50
+ optionalDate(defaultValue?: unknown): ExtractSpec<XlsxStep, 'date'>;
51
+ /** Extract raw cell value, returning default on failure */
52
+ optionalValue(defaultValue?: unknown): ExtractSpec<XlsxStep, 'value'>;
25
53
  /** Move right by count cells (default 1) */
26
54
  right(count?: number): XlsxCellSB;
27
55
  /** Move left by count cells (default 1) */
@@ -39,21 +67,40 @@ export declare class XlsxCellSB {
39
67
  /**
40
68
  * Iterate over cells from this position in a direction while the predicate matches.
41
69
  * Returns an ArraySpec whose items are produced by the itemMapper.
70
+ * The mapper can return a Spec, a FluentSpec, or a plain record (auto-wrapped to ObjectSpec).
42
71
  */
43
72
  eachCell<T extends Spec>(direction: Direction, while_: CellPred, itemMapper: (cell: XlsxCellSB) => T): ArraySpec<T>;
73
+ eachCell(direction: Direction, while_: CellPred, itemMapper: (cell: XlsxCellSB) => Record<string, SpecLike>): ArraySpec<ObjectSpec>;
44
74
  /**
45
75
  * Iterate over slices (rows or columns) from this cell position.
46
76
  * Each slice is passed as an XlsxSliceSB to the mapper, providing
47
77
  * header-relative column/row access via colWhere/rowWhere.
78
+ * The mapper can return a Spec, a FluentSpec, or a plain record (auto-wrapped to ObjectSpec).
79
+ *
80
+ * @param options - Optional `{ groupSize: N }` to step by N columns/rows per slice
48
81
  */
49
- eachSlice<T extends Spec>(direction: Direction, while_: RowPred, itemMapper: (slice: XlsxSliceSB) => T): ArraySpec<T>;
82
+ eachSlice<T extends Spec>(direction: Direction, while_: RowPred, itemMapper: (slice: XlsxSliceSB) => T, options?: EachSliceOptions): ArraySpec<T>;
83
+ eachSlice(direction: Direction, while_: RowPred, itemMapper: (slice: XlsxSliceSB) => Record<string, SpecLike>, options?: EachSliceOptions): ArraySpec<ObjectSpec>;
50
84
  }
85
+ /**
86
+ * XLSX extract type for column definitions.
87
+ */
88
+ export type XlsxExtractType = 'string' | 'number' | 'boolean' | 'date' | 'value' | 'formula';
89
+ /**
90
+ * Column definition for XLSX `columns()` shorthand.
91
+ *
92
+ * - `CellPred` — `colWhere(header, pred).string()`
93
+ * - `[CellPred, XlsxExtractType]` — `colWhere(header, pred).{type}()`
94
+ * - `[number, XlsxExtractType?]` — `col(offset).{type ?? 'string'}()`
95
+ * - `SpecLike` — passthrough (any Spec or FluentSpec)
96
+ */
97
+ export type XlsxColumnDef = CellPred | [CellPred, XlsxExtractType] | [number, XlsxExtractType?] | SpecLike;
51
98
  /**
52
99
  * Spec builder for items within a slice (row or column section).
53
100
  * Provides header-relative column/row access.
54
101
  */
55
- export declare class XlsxSliceSB {
56
- private readonly steps;
102
+ export declare class XlsxSliceSB extends StepBuilder<XlsxStep, 'xlsx'> {
103
+ protected get format(): "xlsx";
57
104
  /** @internal */
58
105
  constructor(steps: readonly XlsxStep[]);
59
106
  /** Access a column by matching its header cell against a predicate (header-relative) */
@@ -64,13 +111,33 @@ export declare class XlsxSliceSB {
64
111
  col(offset: number): XlsxCellSB;
65
112
  /** Access a row by 0-based offset from the anchor (for rotated tables) */
66
113
  row(offset: number): XlsxCellSB;
114
+ /** Navigate to a column whose index comes from a bound variable's CellPosition.col */
115
+ colAt(ref: VariableRefSpec): XlsxCellSB;
116
+ /** Navigate to a row whose index comes from a bound variable's CellPosition.row */
117
+ rowAt(ref: VariableRefSpec): XlsxCellSB;
118
+ /**
119
+ * Extract multiple columns at once, producing an ObjectSpec.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * row.columns(header, {
124
+ * name: cell.equals('Investment'), // colWhere(header, pred).string()
125
+ * amount: [cell.contains('Amount'), 'number'], // colWhere(header, pred).number()
126
+ * note: [2, 'string'], // col(2).string()
127
+ * custom: fluent(row.col(0).string()).map(v => v.trim(), 'trim'), // passthrough
128
+ * })
129
+ * ```
130
+ */
131
+ columns(headerRef: XlsxCellSB, mapping: Record<string, XlsxColumnDef>): ObjectSpec;
132
+ /** @internal Resolve a single column definition to a Spec */
133
+ private _resolveColumnDef;
67
134
  }
68
135
  /**
69
136
  * Spec builder for items within a row (receives an XlsxRange representing one row).
70
137
  * Provides column access to navigate within the row.
71
138
  */
72
- export declare class XlsxRowItemSB {
73
- private readonly steps;
139
+ export declare class XlsxRowItemSB extends StepBuilder<XlsxStep, 'xlsx'> {
140
+ protected get format(): "xlsx";
74
141
  /** @internal */
75
142
  constructor(steps: readonly XlsxStep[]);
76
143
  /** Access a column by 1-based index within the row */
@@ -81,8 +148,8 @@ export declare class XlsxRowItemSB {
81
148
  /**
82
149
  * Spec builder at the range level — cell navigation, rows, and cells collection.
83
150
  */
84
- export declare class XlsxRangeSB {
85
- private readonly steps;
151
+ export declare class XlsxRangeSB extends StepBuilder<XlsxStep, 'xlsx'> {
152
+ protected get format(): "xlsx";
86
153
  /** @internal */
87
154
  constructor(steps: readonly XlsxStep[]);
88
155
  /** Navigate to a cell by address within the range */
@@ -105,8 +172,8 @@ export declare class XlsxRangeSB {
105
172
  /**
106
173
  * Spec builder at the row level.
107
174
  */
108
- export declare class XlsxRowSB {
109
- private readonly steps;
175
+ export declare class XlsxRowSB extends StepBuilder<XlsxStep, 'xlsx'> {
176
+ protected get format(): "xlsx";
110
177
  /** @internal */
111
178
  constructor(steps: readonly XlsxStep[]);
112
179
  /** Navigate to a cell by address within this row */
@@ -117,8 +184,8 @@ export declare class XlsxRowSB {
117
184
  /**
118
185
  * Spec builder at the column level.
119
186
  */
120
- export declare class XlsxColSB {
121
- private readonly steps;
187
+ export declare class XlsxColSB extends StepBuilder<XlsxStep, 'xlsx'> {
188
+ protected get format(): "xlsx";
122
189
  /** @internal */
123
190
  constructor(steps: readonly XlsxStep[]);
124
191
  /** Navigate to a cell by address within this column */
@@ -129,8 +196,8 @@ export declare class XlsxColSB {
129
196
  /**
130
197
  * Spec builder at the sheet level — cell, range, row, column, and search navigation.
131
198
  */
132
- export declare class XlsxSheetSB {
133
- private readonly steps;
199
+ export declare class XlsxSheetSB extends StepBuilder<XlsxStep, 'xlsx'> {
200
+ protected get format(): "xlsx";
134
201
  /** @internal */
135
202
  constructor(steps: readonly XlsxStep[]);
136
203
  /** Navigate to a cell by address (e.g., "A1", "B2") */
@@ -143,6 +210,17 @@ export declare class XlsxSheetSB {
143
210
  findInRow(rowIndex: number, predicate: CellPred): XlsxCellSB;
144
211
  /** Find the first cell matching the predicate in the specified column */
145
212
  findInCol(colIndex: number, predicate: CellPred): XlsxCellSB;
213
+ /**
214
+ * Find all cells matching a predicate, returning CellPosition[].
215
+ * Use with forEach/flatForEach to iterate over discovered positions.
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * const headers = $.firstSheet().findAll(cell.equals('Revenue'))
220
+ * flatForEach(headers, 'hdr', fromRef('hdr').down().eachSlice('down', hasData, ...))
221
+ * ```
222
+ */
223
+ findAll(predicate: CellPred, constraints?: FindAllConstraints): ExtractSpec<XlsxStep, FindAllExtract>;
146
224
  /** Find the first cell matching the predicate in the specified range */
147
225
  findInRange(rangeRef: string, predicate: CellPred): XlsxCellSB;
148
226
  /** Navigate to a range by address (e.g., "A1:C10") */
@@ -161,8 +239,8 @@ export declare class XlsxSheetSB {
161
239
  *
162
240
  * Used as `$` in `emit()` after `.mapIn(parseXlsx())`.
163
241
  */
164
- export declare class XlsxSpecBuilder {
165
- private readonly steps;
242
+ export declare class XlsxSpecBuilder extends StepBuilder<XlsxStep, 'xlsx'> {
243
+ protected get format(): "xlsx";
166
244
  private constructor();
167
245
  /** Create a root spec builder */
168
246
  static root(): XlsxSpecBuilder;
@@ -175,3 +253,17 @@ export declare class XlsxSpecBuilder {
175
253
  /** Find a sheet matching the predicate */
176
254
  findSheet(predicate: SheetPred): XlsxSheetSB;
177
255
  }
256
+ /**
257
+ * Create a cell-level spec builder starting from a variable-bound CellPosition.
258
+ * Use with forEach/flatForEach over findAll results to navigate from discovered positions.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * forEach(
263
+ * $.firstSheet().findAll(cell.equals('Revenue')),
264
+ * 'hdr',
265
+ * fromRef('hdr').down().string()
266
+ * )
267
+ * ```
268
+ */
269
+ export declare function fromRef(ref: string, path?: readonly (string | number)[]): XlsxCellSB;
@@ -1,4 +1,4 @@
1
- import { TYPE_LABEL, ExtractionResult } from '@origints/core';
1
+ import { TYPE_LABEL, ExtractionResult, SpecScope } from '@origints/core';
2
2
  import { XlsxSpec } from './xlsx-spec';
3
3
  import { XlsxWorkbook } from './xlsx-workbook';
4
4
  import { XlsxSheet } from './xlsx-sheet';
@@ -22,4 +22,4 @@ export declare class XlsxSliceContext {
22
22
  * - XlsxRange → starts at range level (used for row items)
23
23
  * - XlsxCell → starts at cell level (used for cell items)
24
24
  */
25
- export declare function executeXlsxSpec(spec: XlsxSpec, data: unknown): ExtractionResult<unknown>;
25
+ export declare function executeXlsxSpec(spec: XlsxSpec, data: unknown, scope?: SpecScope): ExtractionResult<unknown>;