juxscript 1.1.207 → 1.1.209
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/lib/components/table.d.ts.map +1 -1
- package/lib/components/table.js +10 -0
- package/lib/components/table.ts +12 -0
- package/lib/storage/TabularDriver.d.ts +10 -5
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +146 -138
- package/lib/storage/TabularDriver.ts +145 -134
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.ts"],"names":[],"mappings":"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"}
|
package/lib/components/table.js
CHANGED
|
@@ -65,6 +65,16 @@ export class Table extends BaseComponent {
|
|
|
65
65
|
// Configuration methods
|
|
66
66
|
columns(value) {
|
|
67
67
|
this.state.columns = value.map(col => typeof col === 'string' ? { key: col, label: col } : col);
|
|
68
|
+
// ✅ Rebuild header if table already rendered
|
|
69
|
+
if (this._tableElement) {
|
|
70
|
+
const oldThead = this._tableElement.querySelector('thead');
|
|
71
|
+
if (oldThead)
|
|
72
|
+
oldThead.remove();
|
|
73
|
+
if (this.state.headers) {
|
|
74
|
+
const newThead = this._buildTableHeader();
|
|
75
|
+
this._tableElement.insertBefore(newThead, this._tableElement.firstChild);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
68
78
|
return this;
|
|
69
79
|
}
|
|
70
80
|
rows(value) {
|
package/lib/components/table.ts
CHANGED
|
@@ -160,6 +160,18 @@ export class Table extends BaseComponent<TableState> {
|
|
|
160
160
|
this.state.columns = value.map(col =>
|
|
161
161
|
typeof col === 'string' ? { key: col, label: col } : col
|
|
162
162
|
);
|
|
163
|
+
|
|
164
|
+
// ✅ Rebuild header if table already rendered
|
|
165
|
+
if (this._tableElement) {
|
|
166
|
+
const oldThead = this._tableElement.querySelector('thead');
|
|
167
|
+
if (oldThead) oldThead.remove();
|
|
168
|
+
|
|
169
|
+
if (this.state.headers) {
|
|
170
|
+
const newThead = this._buildTableHeader();
|
|
171
|
+
this._tableElement.insertBefore(newThead, this._tableElement.firstChild);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
return this;
|
|
164
176
|
}
|
|
165
177
|
|
|
@@ -49,11 +49,11 @@ export declare class TabularDriver {
|
|
|
49
49
|
*/
|
|
50
50
|
streamFile(file: File, options?: ParseOptions): Promise<DataFrame>;
|
|
51
51
|
/**
|
|
52
|
-
* Parse an XLSX/XLS file into a DataFrame using
|
|
52
|
+
* Parse an XLSX/XLS file into a DataFrame using ExcelJS
|
|
53
53
|
*/
|
|
54
54
|
private _parseXLSX;
|
|
55
55
|
/**
|
|
56
|
-
* Get sheet names from an XLSX file
|
|
56
|
+
* Get sheet names from an XLSX file
|
|
57
57
|
*/
|
|
58
58
|
getSheetNames(file: File): Promise<string[]>;
|
|
59
59
|
/**
|
|
@@ -89,7 +89,7 @@ export declare class TabularDriver {
|
|
|
89
89
|
fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
|
|
90
90
|
/**
|
|
91
91
|
* Read raw cell values from first sheet of an Excel file.
|
|
92
|
-
* Returns rows with their
|
|
92
|
+
* Returns rows with their 0-based row indices.
|
|
93
93
|
* Used by both the preview UI and the parser to ensure consistency.
|
|
94
94
|
*/
|
|
95
95
|
readRawExcelRows(file: File, maxRows?: number): Promise<{
|
|
@@ -97,10 +97,15 @@ export declare class TabularDriver {
|
|
|
97
97
|
values: any[];
|
|
98
98
|
}[]>;
|
|
99
99
|
/**
|
|
100
|
-
*
|
|
101
|
-
* headerRow is
|
|
100
|
+
* Stream Excel file with optional headerRow override.
|
|
101
|
+
* headerRow is 0-based (same as sheetRow from readRawExcelRows).
|
|
102
102
|
*/
|
|
103
103
|
streamFileMultiSheet(file: File, options?: ParseOptions): Promise<Record<string, DataFrame>>;
|
|
104
|
+
/**
|
|
105
|
+
* Extract cell values from an ExcelJS row into a plain array.
|
|
106
|
+
* ExcelJS row.values is 1-based (index 0 is undefined), so we normalize.
|
|
107
|
+
*/
|
|
108
|
+
private _excelRowToArray;
|
|
104
109
|
private _splitLines;
|
|
105
110
|
private _parseLine;
|
|
106
111
|
private _autoType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAsB,EAAE,SAAS,GAAE,MAAiB;IAKlE,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAgCxB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS;IA6D7D;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAoG5E;;OAEG;YACW,UAAU;
|
|
1
|
+
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAsB,EAAE,SAAS,GAAE,MAAiB;IAKlE,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAgCxB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS;IA6D7D;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAoG5E;;OAEG;YACW,UAAU;IA6ExB;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAkBlD;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBzF;;OAEG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAwBjD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA4BzD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqBlH;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5B;;OAEG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IA0ExE;;;;OAIG;IACG,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,MAAW,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,GAAG,EAAE,CAAA;KAAE,EAAE,CAAC;IAsCxG;;;OAGG;IACG,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAiHtG;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,UAAU;IAmClB,OAAO,CAAC,SAAS;IAYjB,KAAK,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAEhF"}
|
|
@@ -210,66 +210,73 @@ export class TabularDriver {
|
|
|
210
210
|
return df;
|
|
211
211
|
}
|
|
212
212
|
/* ═══════════════════════════════════════════════════
|
|
213
|
-
* XLSX / XLS PARSING
|
|
213
|
+
* XLSX / XLS PARSING (ExcelJS)
|
|
214
214
|
* ═══════════════════════════════════════════════════ */
|
|
215
215
|
/**
|
|
216
|
-
* Parse an XLSX/XLS file into a DataFrame using
|
|
216
|
+
* Parse an XLSX/XLS file into a DataFrame using ExcelJS
|
|
217
217
|
*/
|
|
218
218
|
async _parseXLSX(file, options = {}) {
|
|
219
219
|
const { maxRows, skipRows = 0, columns: selectCols, sheet, onProgress, hasHeader = true } = options;
|
|
220
|
-
|
|
221
|
-
let XLSX;
|
|
220
|
+
let ExcelJS;
|
|
222
221
|
try {
|
|
223
|
-
|
|
222
|
+
ExcelJS = await import('exceljs');
|
|
224
223
|
}
|
|
225
224
|
catch {
|
|
226
|
-
throw new Error('XLSX support requires the "
|
|
225
|
+
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
227
226
|
}
|
|
228
227
|
if (onProgress)
|
|
229
228
|
onProgress(0, file.size);
|
|
230
229
|
const buffer = await file.arrayBuffer();
|
|
231
230
|
if (onProgress)
|
|
232
231
|
onProgress(file.size * 0.5, file.size);
|
|
233
|
-
const workbook =
|
|
232
|
+
const workbook = new ExcelJS.Workbook();
|
|
233
|
+
await workbook.xlsx.load(buffer);
|
|
234
234
|
// Select sheet
|
|
235
|
-
let
|
|
235
|
+
let worksheet;
|
|
236
236
|
if (typeof sheet === 'number') {
|
|
237
|
-
|
|
237
|
+
worksheet = workbook.worksheets[sheet] || workbook.worksheets[0];
|
|
238
238
|
}
|
|
239
239
|
else if (typeof sheet === 'string') {
|
|
240
|
-
|
|
240
|
+
worksheet = workbook.getWorksheet(sheet) || workbook.worksheets[0];
|
|
241
241
|
}
|
|
242
242
|
else {
|
|
243
|
-
|
|
243
|
+
worksheet = workbook.worksheets[0];
|
|
244
244
|
}
|
|
245
|
-
const worksheet = workbook.Sheets[sheetName];
|
|
246
245
|
if (!worksheet) {
|
|
247
|
-
|
|
246
|
+
const names = workbook.worksheets.map((ws) => ws.name).join(', ');
|
|
247
|
+
throw new Error(`No worksheet found. Available: ${names}`);
|
|
248
248
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
249
|
+
if (onProgress)
|
|
250
|
+
onProgress(file.size * 0.7, file.size);
|
|
251
|
+
// Read all rows
|
|
252
|
+
const allRows = [];
|
|
253
|
+
let headers = null;
|
|
254
|
+
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
|
|
255
|
+
const values = this._excelRowToArray(row, worksheet.columnCount);
|
|
256
|
+
if (!headers && hasHeader) {
|
|
257
|
+
headers = values.map((v, i) => {
|
|
258
|
+
if (v === null || v === undefined || String(v).trim() === '')
|
|
259
|
+
return `col_${i}`;
|
|
260
|
+
return String(v).trim();
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!headers) {
|
|
265
|
+
headers = values.map((_, i) => `col_${i}`);
|
|
266
|
+
}
|
|
267
|
+
const rowObj = {};
|
|
268
|
+
headers.forEach((h, j) => {
|
|
269
|
+
rowObj[h] = this._autoType(values[j] === null || values[j] === undefined ? '' : String(values[j]));
|
|
270
|
+
});
|
|
271
|
+
allRows.push(rowObj);
|
|
254
272
|
});
|
|
255
273
|
if (onProgress)
|
|
256
|
-
onProgress(file.size * 0.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (skipRows > 0) {
|
|
274
|
+
onProgress(file.size * 0.9, file.size);
|
|
275
|
+
let rows = allRows;
|
|
276
|
+
if (skipRows > 0)
|
|
260
277
|
rows = rows.slice(skipRows);
|
|
261
|
-
|
|
262
|
-
if (maxRows !== undefined) {
|
|
278
|
+
if (maxRows !== undefined)
|
|
263
279
|
rows = rows.slice(0, maxRows);
|
|
264
|
-
}
|
|
265
|
-
// Auto-type values
|
|
266
|
-
rows = rows.map(row => {
|
|
267
|
-
const typed = {};
|
|
268
|
-
for (const [key, value] of Object.entries(row)) {
|
|
269
|
-
typed[key] = this._autoType(value === null || value === undefined ? '' : String(value));
|
|
270
|
-
}
|
|
271
|
-
return typed;
|
|
272
|
-
});
|
|
273
280
|
if (onProgress)
|
|
274
281
|
onProgress(file.size, file.size);
|
|
275
282
|
let df = new DataFrame(rows);
|
|
@@ -278,19 +285,20 @@ export class TabularDriver {
|
|
|
278
285
|
return df;
|
|
279
286
|
}
|
|
280
287
|
/**
|
|
281
|
-
* Get sheet names from an XLSX file
|
|
288
|
+
* Get sheet names from an XLSX file
|
|
282
289
|
*/
|
|
283
290
|
async getSheetNames(file) {
|
|
284
|
-
let
|
|
291
|
+
let ExcelJS;
|
|
285
292
|
try {
|
|
286
|
-
|
|
293
|
+
ExcelJS = await import('exceljs');
|
|
287
294
|
}
|
|
288
295
|
catch {
|
|
289
|
-
throw new Error('XLSX support requires the "
|
|
296
|
+
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
290
297
|
}
|
|
291
298
|
const buffer = await file.arrayBuffer();
|
|
292
|
-
const workbook =
|
|
293
|
-
|
|
299
|
+
const workbook = new ExcelJS.Workbook();
|
|
300
|
+
await workbook.xlsx.load(buffer);
|
|
301
|
+
return workbook.worksheets.map((ws) => ws.name);
|
|
294
302
|
}
|
|
295
303
|
/* ═══════════════════════════════════════════════════
|
|
296
304
|
* INDEXEDDB PERSISTENCE
|
|
@@ -488,130 +496,86 @@ export class TabularDriver {
|
|
|
488
496
|
}
|
|
489
497
|
/**
|
|
490
498
|
* Read raw cell values from first sheet of an Excel file.
|
|
491
|
-
* Returns rows with their
|
|
499
|
+
* Returns rows with their 0-based row indices.
|
|
492
500
|
* Used by both the preview UI and the parser to ensure consistency.
|
|
493
501
|
*/
|
|
494
502
|
async readRawExcelRows(file, maxRows = 15) {
|
|
495
|
-
let
|
|
503
|
+
let ExcelJS;
|
|
496
504
|
try {
|
|
497
|
-
|
|
505
|
+
ExcelJS = await import('exceljs');
|
|
498
506
|
}
|
|
499
507
|
catch {
|
|
500
|
-
throw new Error('XLSX support requires the "
|
|
508
|
+
throw new Error('XLSX support requires the "exceljs" package.');
|
|
501
509
|
}
|
|
502
510
|
const buffer = await file.arrayBuffer();
|
|
503
|
-
const workbook =
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
});
|
|
508
|
-
const sheetName = workbook.SheetNames[0];
|
|
509
|
-
const worksheet = workbook.Sheets[sheetName];
|
|
510
|
-
const ref = worksheet['!ref'];
|
|
511
|
-
if (!ref)
|
|
511
|
+
const workbook = new ExcelJS.Workbook();
|
|
512
|
+
await workbook.xlsx.load(buffer);
|
|
513
|
+
const worksheet = workbook.worksheets[0];
|
|
514
|
+
if (!worksheet)
|
|
512
515
|
return [];
|
|
513
|
-
const
|
|
514
|
-
const startRow = range.s.r;
|
|
515
|
-
const endRow = Math.min(range.e.r, startRow + maxRows - 1);
|
|
516
|
-
const startCol = range.s.c;
|
|
517
|
-
const endCol = range.e.c;
|
|
518
|
-
console.log(`[readRawExcelRows] ref=${ref}, startRow=${startRow}, endRow=${endRow}, cols=${startCol}-${endCol}`);
|
|
516
|
+
const colCount = worksheet.columnCount;
|
|
519
517
|
const rows = [];
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
if (cell.v !== undefined) {
|
|
534
|
-
values.push(cell.v);
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
values.push(null);
|
|
538
|
-
}
|
|
539
|
-
rows.push({ sheetRow: r, values });
|
|
518
|
+
let count = 0;
|
|
519
|
+
// ExcelJS eachRow gives 1-based rowNumber; we expose 0-based to the UI
|
|
520
|
+
// But we need to also show truly empty rows, so iterate by index
|
|
521
|
+
const totalRows = Math.min(worksheet.rowCount, maxRows);
|
|
522
|
+
for (let r = 1; r <= totalRows; r++) {
|
|
523
|
+
const row = worksheet.getRow(r);
|
|
524
|
+
const values = this._excelRowToArray(row, colCount);
|
|
525
|
+
// sheetRow is 0-based (r-1) to match what we pass back to streamFileMultiSheet
|
|
526
|
+
rows.push({ sheetRow: r - 1, values });
|
|
527
|
+
count++;
|
|
528
|
+
if (count >= maxRows)
|
|
529
|
+
break;
|
|
540
530
|
}
|
|
531
|
+
console.log(`[readRawExcelRows] ${rows.length} rows, colCount=${colCount}`);
|
|
532
|
+
rows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
|
|
541
533
|
return rows;
|
|
542
534
|
}
|
|
543
535
|
/**
|
|
544
|
-
*
|
|
545
|
-
* headerRow is
|
|
536
|
+
* Stream Excel file with optional headerRow override.
|
|
537
|
+
* headerRow is 0-based (same as sheetRow from readRawExcelRows).
|
|
546
538
|
*/
|
|
547
539
|
async streamFileMultiSheet(file, options = {}) {
|
|
548
|
-
const { maxSheetSize = 100000,
|
|
549
|
-
let
|
|
540
|
+
const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
|
|
541
|
+
let ExcelJS;
|
|
550
542
|
try {
|
|
551
|
-
|
|
543
|
+
ExcelJS = await import('exceljs');
|
|
552
544
|
}
|
|
553
545
|
catch {
|
|
554
|
-
throw new Error('XLSX support requires the "
|
|
546
|
+
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
555
547
|
}
|
|
556
548
|
if (onProgress)
|
|
557
549
|
onProgress(0, file.size);
|
|
558
550
|
const buffer = await file.arrayBuffer();
|
|
559
551
|
if (onProgress)
|
|
560
552
|
onProgress(file.size * 0.3, file.size);
|
|
561
|
-
const workbook =
|
|
562
|
-
|
|
563
|
-
sheetRows: maxSheetSize,
|
|
564
|
-
dense: false
|
|
565
|
-
});
|
|
553
|
+
const workbook = new ExcelJS.Workbook();
|
|
554
|
+
await workbook.xlsx.load(buffer);
|
|
566
555
|
const sheets = {};
|
|
567
|
-
const totalSheets = workbook.
|
|
556
|
+
const totalSheets = workbook.worksheets.length;
|
|
568
557
|
let processedSheets = 0;
|
|
569
|
-
for (const
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
const range = XLSX.utils.decode_range(ref);
|
|
577
|
-
const startRow = range.s.r;
|
|
578
|
-
const endRow = range.e.r;
|
|
579
|
-
const startCol = range.s.c;
|
|
580
|
-
const endCol = range.e.c;
|
|
581
|
-
if (endRow < startRow) {
|
|
558
|
+
for (const worksheet of workbook.worksheets) {
|
|
559
|
+
const sheetName = worksheet.name;
|
|
560
|
+
const colCount = worksheet.columnCount;
|
|
561
|
+
const rowCount = worksheet.rowCount;
|
|
562
|
+
if (rowCount === 0 || colCount === 0) {
|
|
582
563
|
processedSheets++;
|
|
583
564
|
continue;
|
|
584
565
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return cell.w;
|
|
592
|
-
if (cell.v !== undefined)
|
|
593
|
-
return cell.v;
|
|
594
|
-
return null;
|
|
595
|
-
};
|
|
596
|
-
const readRow = (r) => {
|
|
597
|
-
const vals = [];
|
|
598
|
-
for (let c = startCol; c <= endCol; c++) {
|
|
599
|
-
vals.push(readCellValue(r, c));
|
|
600
|
-
}
|
|
601
|
-
return vals;
|
|
602
|
-
};
|
|
603
|
-
// headerRow is the absolute sheet row index — same value as
|
|
604
|
-
// sheetRow from readRawExcelRows(). No offset needed.
|
|
605
|
-
const headerSheetRow = headerRow;
|
|
606
|
-
console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}`);
|
|
607
|
-
console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow}`);
|
|
608
|
-
if (headerSheetRow > endRow) {
|
|
609
|
-
console.warn(`[TabularDriver] headerRow ${headerRow} exceeds endRow ${endRow}`);
|
|
566
|
+
// headerRow is 0-based; ExcelJS rows are 1-based
|
|
567
|
+
const headerExcelRow = headerRow + 1;
|
|
568
|
+
console.log(`[TabularDriver] Sheet "${sheetName}": rowCount=${rowCount}, colCount=${colCount}`);
|
|
569
|
+
console.log(`[TabularDriver] headerRow=${headerRow} (0-based), excelRow=${headerExcelRow} (1-based)`);
|
|
570
|
+
if (headerExcelRow > rowCount) {
|
|
571
|
+
console.warn(`[TabularDriver] headerRow ${headerRow} exceeds rowCount ${rowCount}`);
|
|
610
572
|
processedSheets++;
|
|
611
573
|
continue;
|
|
612
574
|
}
|
|
613
|
-
|
|
614
|
-
|
|
575
|
+
// Read header row
|
|
576
|
+
const headerRowObj = worksheet.getRow(headerExcelRow);
|
|
577
|
+
const headerValues = this._excelRowToArray(headerRowObj, colCount);
|
|
578
|
+
console.log(`[TabularDriver] Raw header values at row ${headerRow}:`, headerValues);
|
|
615
579
|
const headers = headerValues.map((h, i) => {
|
|
616
580
|
if (h === null || h === undefined || String(h).trim() === '') {
|
|
617
581
|
return `__EMPTY${i > 0 ? '_' + i : ''}`;
|
|
@@ -622,25 +586,34 @@ export class TabularDriver {
|
|
|
622
586
|
console.log(`[TabularDriver] Headers (${validHeaders.length} valid / ${headers.length} total):`, headers);
|
|
623
587
|
if (validHeaders.length === 0) {
|
|
624
588
|
console.warn(`[TabularDriver] No valid headers found at row ${headerRow} in sheet "${sheetName}"`);
|
|
625
|
-
|
|
626
|
-
|
|
589
|
+
// Debug surrounding rows
|
|
590
|
+
for (let debugR = Math.max(1, headerExcelRow - 2); debugR <= Math.min(rowCount, headerExcelRow + 2); debugR++) {
|
|
591
|
+
const debugRow = worksheet.getRow(debugR);
|
|
592
|
+
console.log(`[TabularDriver] row ${debugR - 1}:`, this._excelRowToArray(debugRow, colCount));
|
|
627
593
|
}
|
|
628
594
|
processedSheets++;
|
|
629
595
|
continue;
|
|
630
596
|
}
|
|
597
|
+
// Build data rows: everything after the header row
|
|
631
598
|
const rows = [];
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
599
|
+
const maxDataRow = Math.min(rowCount, headerExcelRow + maxSheetSize);
|
|
600
|
+
for (let r = headerExcelRow + 1; r <= maxDataRow; r++) {
|
|
601
|
+
const row = worksheet.getRow(r);
|
|
602
|
+
const rowData = this._excelRowToArray(row, colCount);
|
|
603
|
+
// Skip completely empty rows
|
|
604
|
+
const hasContent = rowData.some((cell) => cell !== null && cell !== undefined && String(cell).trim() !== '');
|
|
635
605
|
if (!hasContent)
|
|
636
606
|
continue;
|
|
637
|
-
const
|
|
607
|
+
const rowObj = {};
|
|
638
608
|
for (let j = 0; j < headers.length; j++) {
|
|
639
|
-
|
|
609
|
+
rowObj[headers[j]] = rowData[j];
|
|
640
610
|
}
|
|
641
|
-
rows.push(
|
|
611
|
+
rows.push(rowObj);
|
|
642
612
|
}
|
|
643
613
|
console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
|
|
614
|
+
if (rows.length > 0) {
|
|
615
|
+
console.log(`[TabularDriver] First row:`, rows[0]);
|
|
616
|
+
}
|
|
644
617
|
if (rows.length > 0) {
|
|
645
618
|
sheets[sheetName] = new DataFrame(rows);
|
|
646
619
|
}
|
|
@@ -654,6 +627,41 @@ export class TabularDriver {
|
|
|
654
627
|
onProgress(file.size, file.size);
|
|
655
628
|
return sheets;
|
|
656
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Extract cell values from an ExcelJS row into a plain array.
|
|
632
|
+
* ExcelJS row.values is 1-based (index 0 is undefined), so we normalize.
|
|
633
|
+
*/
|
|
634
|
+
_excelRowToArray(row, colCount) {
|
|
635
|
+
const values = [];
|
|
636
|
+
for (let c = 1; c <= colCount; c++) {
|
|
637
|
+
const cell = row.getCell(c);
|
|
638
|
+
if (!cell || cell.value === null || cell.value === undefined) {
|
|
639
|
+
values.push(null);
|
|
640
|
+
}
|
|
641
|
+
else if (cell.type === 8 /* FormulaType */ && cell.result !== undefined) {
|
|
642
|
+
values.push(cell.result);
|
|
643
|
+
}
|
|
644
|
+
else if (typeof cell.value === 'object' && cell.value !== null) {
|
|
645
|
+
// Handle rich text, hyperlinks, etc.
|
|
646
|
+
if (cell.value.richText) {
|
|
647
|
+
values.push(cell.value.richText.map((rt) => rt.text).join(''));
|
|
648
|
+
}
|
|
649
|
+
else if (cell.value.text) {
|
|
650
|
+
values.push(cell.value.text);
|
|
651
|
+
}
|
|
652
|
+
else if (cell.value.hyperlink) {
|
|
653
|
+
values.push(cell.value.text || cell.value.hyperlink);
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
values.push(String(cell.value));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
values.push(cell.value);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return values;
|
|
664
|
+
}
|
|
657
665
|
/* ═══════════════════════════════════════════════════
|
|
658
666
|
* INTERNAL PARSING HELPERS
|
|
659
667
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -293,22 +293,21 @@ export class TabularDriver {
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
/* ═══════════════════════════════════════════════════
|
|
296
|
-
* XLSX / XLS PARSING
|
|
296
|
+
* XLSX / XLS PARSING (ExcelJS)
|
|
297
297
|
* ═══════════════════════════════════════════════════ */
|
|
298
298
|
|
|
299
299
|
/**
|
|
300
|
-
* Parse an XLSX/XLS file into a DataFrame using
|
|
300
|
+
* Parse an XLSX/XLS file into a DataFrame using ExcelJS
|
|
301
301
|
*/
|
|
302
302
|
private async _parseXLSX(file: File, options: ParseOptions = {}): Promise<DataFrame> {
|
|
303
303
|
const { maxRows, skipRows = 0, columns: selectCols, sheet, onProgress, hasHeader = true } = options;
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
let XLSX: any;
|
|
305
|
+
let ExcelJS: any;
|
|
307
306
|
try {
|
|
308
|
-
|
|
307
|
+
ExcelJS = await import('exceljs');
|
|
309
308
|
} catch {
|
|
310
309
|
throw new Error(
|
|
311
|
-
'XLSX support requires the "
|
|
310
|
+
'XLSX support requires the "exceljs" package. Install it with: npm install exceljs'
|
|
312
311
|
);
|
|
313
312
|
}
|
|
314
313
|
|
|
@@ -318,72 +317,80 @@ export class TabularDriver {
|
|
|
318
317
|
|
|
319
318
|
if (onProgress) onProgress(file.size * 0.5, file.size);
|
|
320
319
|
|
|
321
|
-
const workbook =
|
|
320
|
+
const workbook = new ExcelJS.Workbook();
|
|
321
|
+
await workbook.xlsx.load(buffer);
|
|
322
322
|
|
|
323
323
|
// Select sheet
|
|
324
|
-
let
|
|
324
|
+
let worksheet: any;
|
|
325
325
|
if (typeof sheet === 'number') {
|
|
326
|
-
|
|
326
|
+
worksheet = workbook.worksheets[sheet] || workbook.worksheets[0];
|
|
327
327
|
} else if (typeof sheet === 'string') {
|
|
328
|
-
|
|
328
|
+
worksheet = workbook.getWorksheet(sheet) || workbook.worksheets[0];
|
|
329
329
|
} else {
|
|
330
|
-
|
|
330
|
+
worksheet = workbook.worksheets[0];
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
const worksheet = workbook.Sheets[sheetName];
|
|
334
333
|
if (!worksheet) {
|
|
335
|
-
|
|
334
|
+
const names = workbook.worksheets.map((ws: any) => ws.name).join(', ');
|
|
335
|
+
throw new Error(`No worksheet found. Available: ${names}`);
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
|
|
339
|
-
const jsonRows: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
|
|
340
|
-
header: hasHeader ? undefined : 1,
|
|
341
|
-
defval: null,
|
|
342
|
-
raw: true
|
|
343
|
-
});
|
|
338
|
+
if (onProgress) onProgress(file.size * 0.7, file.size);
|
|
344
339
|
|
|
345
|
-
|
|
340
|
+
// Read all rows
|
|
341
|
+
const allRows: Record<string, any>[] = [];
|
|
342
|
+
let headers: string[] | null = null;
|
|
346
343
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
344
|
+
worksheet.eachRow({ includeEmpty: false }, (row: any, rowNumber: number) => {
|
|
345
|
+
const values = this._excelRowToArray(row, worksheet.columnCount);
|
|
346
|
+
|
|
347
|
+
if (!headers && hasHeader) {
|
|
348
|
+
headers = values.map((v: any, i: number) => {
|
|
349
|
+
if (v === null || v === undefined || String(v).trim() === '') return `col_${i}`;
|
|
350
|
+
return String(v).trim();
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
355
354
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const typed: Record<string, any> = {};
|
|
359
|
-
for (const [key, value] of Object.entries(row)) {
|
|
360
|
-
typed[key] = this._autoType(value === null || value === undefined ? '' : String(value));
|
|
355
|
+
if (!headers) {
|
|
356
|
+
headers = values.map((_: any, i: number) => `col_${i}`);
|
|
361
357
|
}
|
|
362
|
-
|
|
358
|
+
|
|
359
|
+
const rowObj: Record<string, any> = {};
|
|
360
|
+
headers.forEach((h, j) => {
|
|
361
|
+
rowObj[h] = this._autoType(values[j] === null || values[j] === undefined ? '' : String(values[j]));
|
|
362
|
+
});
|
|
363
|
+
allRows.push(rowObj);
|
|
363
364
|
});
|
|
364
365
|
|
|
366
|
+
if (onProgress) onProgress(file.size * 0.9, file.size);
|
|
367
|
+
|
|
368
|
+
let rows = allRows;
|
|
369
|
+
if (skipRows > 0) rows = rows.slice(skipRows);
|
|
370
|
+
if (maxRows !== undefined) rows = rows.slice(0, maxRows);
|
|
371
|
+
|
|
365
372
|
if (onProgress) onProgress(file.size, file.size);
|
|
366
373
|
|
|
367
374
|
let df = new DataFrame(rows);
|
|
368
375
|
if (selectCols) df = df.select(...selectCols);
|
|
369
|
-
|
|
370
376
|
return df;
|
|
371
377
|
}
|
|
372
378
|
|
|
373
379
|
/**
|
|
374
|
-
* Get sheet names from an XLSX file
|
|
380
|
+
* Get sheet names from an XLSX file
|
|
375
381
|
*/
|
|
376
382
|
async getSheetNames(file: File): Promise<string[]> {
|
|
377
|
-
let
|
|
383
|
+
let ExcelJS: any;
|
|
378
384
|
try {
|
|
379
|
-
|
|
385
|
+
ExcelJS = await import('exceljs');
|
|
380
386
|
} catch {
|
|
381
|
-
throw new Error('XLSX support requires the "
|
|
387
|
+
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
382
388
|
}
|
|
383
389
|
|
|
384
390
|
const buffer = await file.arrayBuffer();
|
|
385
|
-
const workbook =
|
|
386
|
-
|
|
391
|
+
const workbook = new ExcelJS.Workbook();
|
|
392
|
+
await workbook.xlsx.load(buffer);
|
|
393
|
+
return workbook.worksheets.map((ws: any) => ws.name);
|
|
387
394
|
}
|
|
388
395
|
|
|
389
396
|
/* ═══════════════════════════════════════════════════
|
|
@@ -608,66 +615,59 @@ export class TabularDriver {
|
|
|
608
615
|
|
|
609
616
|
/**
|
|
610
617
|
* Read raw cell values from first sheet of an Excel file.
|
|
611
|
-
* Returns rows with their
|
|
618
|
+
* Returns rows with their 0-based row indices.
|
|
612
619
|
* Used by both the preview UI and the parser to ensure consistency.
|
|
613
620
|
*/
|
|
614
621
|
async readRawExcelRows(file: File, maxRows: number = 15): Promise<{ sheetRow: number; values: any[] }[]> {
|
|
615
|
-
let
|
|
622
|
+
let ExcelJS: any;
|
|
616
623
|
try {
|
|
617
|
-
|
|
624
|
+
ExcelJS = await import('exceljs');
|
|
618
625
|
} catch {
|
|
619
|
-
throw new Error('XLSX support requires the "
|
|
626
|
+
throw new Error('XLSX support requires the "exceljs" package.');
|
|
620
627
|
}
|
|
621
628
|
|
|
622
629
|
const buffer = await file.arrayBuffer();
|
|
623
|
-
const workbook =
|
|
624
|
-
|
|
625
|
-
sheetRows: maxRows + 5,
|
|
626
|
-
dense: false
|
|
627
|
-
});
|
|
630
|
+
const workbook = new ExcelJS.Workbook();
|
|
631
|
+
await workbook.xlsx.load(buffer);
|
|
628
632
|
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
const ref = worksheet['!ref'];
|
|
632
|
-
if (!ref) return [];
|
|
633
|
-
|
|
634
|
-
const range = XLSX.utils.decode_range(ref);
|
|
635
|
-
const startRow = range.s.r;
|
|
636
|
-
const endRow = Math.min(range.e.r, startRow + maxRows - 1);
|
|
637
|
-
const startCol = range.s.c;
|
|
638
|
-
const endCol = range.e.c;
|
|
639
|
-
|
|
640
|
-
console.log(`[readRawExcelRows] ref=${ref}, startRow=${startRow}, endRow=${endRow}, cols=${startCol}-${endCol}`);
|
|
633
|
+
const worksheet = workbook.worksheets[0];
|
|
634
|
+
if (!worksheet) return [];
|
|
641
635
|
|
|
636
|
+
const colCount = worksheet.columnCount;
|
|
642
637
|
const rows: { sheetRow: number; values: any[] }[] = [];
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
rows.push({ sheetRow: r, values });
|
|
638
|
+
let count = 0;
|
|
639
|
+
|
|
640
|
+
// ExcelJS eachRow gives 1-based rowNumber; we expose 0-based to the UI
|
|
641
|
+
// But we need to also show truly empty rows, so iterate by index
|
|
642
|
+
const totalRows = Math.min(worksheet.rowCount, maxRows);
|
|
643
|
+
|
|
644
|
+
for (let r = 1; r <= totalRows; r++) {
|
|
645
|
+
const row = worksheet.getRow(r);
|
|
646
|
+
const values = this._excelRowToArray(row, colCount);
|
|
647
|
+
// sheetRow is 0-based (r-1) to match what we pass back to streamFileMultiSheet
|
|
648
|
+
rows.push({ sheetRow: r - 1, values });
|
|
649
|
+
count++;
|
|
650
|
+
if (count >= maxRows) break;
|
|
654
651
|
}
|
|
655
652
|
|
|
653
|
+
console.log(`[readRawExcelRows] ${rows.length} rows, colCount=${colCount}`);
|
|
654
|
+
rows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
|
|
655
|
+
|
|
656
656
|
return rows;
|
|
657
657
|
}
|
|
658
658
|
|
|
659
659
|
/**
|
|
660
|
-
*
|
|
661
|
-
* headerRow is
|
|
660
|
+
* Stream Excel file with optional headerRow override.
|
|
661
|
+
* headerRow is 0-based (same as sheetRow from readRawExcelRows).
|
|
662
662
|
*/
|
|
663
663
|
async streamFileMultiSheet(file: File, options: ParseOptions = {}): Promise<Record<string, DataFrame>> {
|
|
664
|
-
const { maxSheetSize = 100000,
|
|
664
|
+
const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
|
|
665
665
|
|
|
666
|
-
let
|
|
666
|
+
let ExcelJS: any;
|
|
667
667
|
try {
|
|
668
|
-
|
|
668
|
+
ExcelJS = await import('exceljs');
|
|
669
669
|
} catch {
|
|
670
|
-
throw new Error('XLSX support requires the "
|
|
670
|
+
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
if (onProgress) onProgress(0, file.size);
|
|
@@ -676,68 +676,39 @@ export class TabularDriver {
|
|
|
676
676
|
|
|
677
677
|
if (onProgress) onProgress(file.size * 0.3, file.size);
|
|
678
678
|
|
|
679
|
-
const workbook =
|
|
680
|
-
|
|
681
|
-
sheetRows: maxSheetSize,
|
|
682
|
-
dense: false
|
|
683
|
-
});
|
|
679
|
+
const workbook = new ExcelJS.Workbook();
|
|
680
|
+
await workbook.xlsx.load(buffer);
|
|
684
681
|
|
|
685
682
|
const sheets: Record<string, DataFrame> = {};
|
|
686
|
-
const totalSheets = workbook.
|
|
683
|
+
const totalSheets = workbook.worksheets.length;
|
|
687
684
|
let processedSheets = 0;
|
|
688
685
|
|
|
689
|
-
for (const
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
if (!ref) {
|
|
694
|
-
processedSheets++;
|
|
695
|
-
continue;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
const range = XLSX.utils.decode_range(ref);
|
|
699
|
-
const startRow = range.s.r;
|
|
700
|
-
const endRow = range.e.r;
|
|
701
|
-
const startCol = range.s.c;
|
|
702
|
-
const endCol = range.e.c;
|
|
686
|
+
for (const worksheet of workbook.worksheets) {
|
|
687
|
+
const sheetName = worksheet.name;
|
|
688
|
+
const colCount = worksheet.columnCount;
|
|
689
|
+
const rowCount = worksheet.rowCount;
|
|
703
690
|
|
|
704
|
-
if (
|
|
691
|
+
if (rowCount === 0 || colCount === 0) {
|
|
705
692
|
processedSheets++;
|
|
706
693
|
continue;
|
|
707
694
|
}
|
|
708
695
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const cell = worksheet[addr];
|
|
712
|
-
if (!cell) return null;
|
|
713
|
-
if (cell.w !== undefined) return cell.w;
|
|
714
|
-
if (cell.v !== undefined) return cell.v;
|
|
715
|
-
return null;
|
|
716
|
-
};
|
|
717
|
-
|
|
718
|
-
const readRow = (r: number): any[] => {
|
|
719
|
-
const vals: any[] = [];
|
|
720
|
-
for (let c = startCol; c <= endCol; c++) {
|
|
721
|
-
vals.push(readCellValue(r, c));
|
|
722
|
-
}
|
|
723
|
-
return vals;
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
// headerRow is the absolute sheet row index — same value as
|
|
727
|
-
// sheetRow from readRawExcelRows(). No offset needed.
|
|
728
|
-
const headerSheetRow = headerRow;
|
|
696
|
+
// headerRow is 0-based; ExcelJS rows are 1-based
|
|
697
|
+
const headerExcelRow = headerRow + 1;
|
|
729
698
|
|
|
730
|
-
console.log(`[TabularDriver] Sheet "${sheetName}":
|
|
731
|
-
console.log(`[TabularDriver] headerRow=${headerRow},
|
|
699
|
+
console.log(`[TabularDriver] Sheet "${sheetName}": rowCount=${rowCount}, colCount=${colCount}`);
|
|
700
|
+
console.log(`[TabularDriver] headerRow=${headerRow} (0-based), excelRow=${headerExcelRow} (1-based)`);
|
|
732
701
|
|
|
733
|
-
if (
|
|
734
|
-
console.warn(`[TabularDriver] headerRow ${headerRow} exceeds
|
|
702
|
+
if (headerExcelRow > rowCount) {
|
|
703
|
+
console.warn(`[TabularDriver] headerRow ${headerRow} exceeds rowCount ${rowCount}`);
|
|
735
704
|
processedSheets++;
|
|
736
705
|
continue;
|
|
737
706
|
}
|
|
738
707
|
|
|
739
|
-
|
|
740
|
-
|
|
708
|
+
// Read header row
|
|
709
|
+
const headerRowObj = worksheet.getRow(headerExcelRow);
|
|
710
|
+
const headerValues = this._excelRowToArray(headerRowObj, colCount);
|
|
711
|
+
console.log(`[TabularDriver] Raw header values at row ${headerRow}:`, headerValues);
|
|
741
712
|
|
|
742
713
|
const headers: string[] = headerValues.map((h: any, i: number) => {
|
|
743
714
|
if (h === null || h === undefined || String(h).trim() === '') {
|
|
@@ -751,30 +722,40 @@ export class TabularDriver {
|
|
|
751
722
|
|
|
752
723
|
if (validHeaders.length === 0) {
|
|
753
724
|
console.warn(`[TabularDriver] No valid headers found at row ${headerRow} in sheet "${sheetName}"`);
|
|
754
|
-
|
|
755
|
-
|
|
725
|
+
// Debug surrounding rows
|
|
726
|
+
for (let debugR = Math.max(1, headerExcelRow - 2); debugR <= Math.min(rowCount, headerExcelRow + 2); debugR++) {
|
|
727
|
+
const debugRow = worksheet.getRow(debugR);
|
|
728
|
+
console.log(`[TabularDriver] row ${debugR - 1}:`, this._excelRowToArray(debugRow, colCount));
|
|
756
729
|
}
|
|
757
730
|
processedSheets++;
|
|
758
731
|
continue;
|
|
759
732
|
}
|
|
760
733
|
|
|
734
|
+
// Build data rows: everything after the header row
|
|
761
735
|
const rows: Record<string, any>[] = [];
|
|
762
|
-
|
|
763
|
-
|
|
736
|
+
const maxDataRow = Math.min(rowCount, headerExcelRow + maxSheetSize);
|
|
737
|
+
|
|
738
|
+
for (let r = headerExcelRow + 1; r <= maxDataRow; r++) {
|
|
739
|
+
const row = worksheet.getRow(r);
|
|
740
|
+
const rowData = this._excelRowToArray(row, colCount);
|
|
764
741
|
|
|
765
|
-
|
|
742
|
+
// Skip completely empty rows
|
|
743
|
+
const hasContent = rowData.some((cell: any) =>
|
|
766
744
|
cell !== null && cell !== undefined && String(cell).trim() !== ''
|
|
767
745
|
);
|
|
768
746
|
if (!hasContent) continue;
|
|
769
747
|
|
|
770
|
-
const
|
|
748
|
+
const rowObj: Record<string, any> = {};
|
|
771
749
|
for (let j = 0; j < headers.length; j++) {
|
|
772
|
-
|
|
750
|
+
rowObj[headers[j]] = rowData[j];
|
|
773
751
|
}
|
|
774
|
-
rows.push(
|
|
752
|
+
rows.push(rowObj);
|
|
775
753
|
}
|
|
776
754
|
|
|
777
755
|
console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
|
|
756
|
+
if (rows.length > 0) {
|
|
757
|
+
console.log(`[TabularDriver] First row:`, rows[0]);
|
|
758
|
+
}
|
|
778
759
|
|
|
779
760
|
if (rows.length > 0) {
|
|
780
761
|
sheets[sheetName] = new DataFrame(rows);
|
|
@@ -792,6 +773,36 @@ export class TabularDriver {
|
|
|
792
773
|
return sheets;
|
|
793
774
|
}
|
|
794
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Extract cell values from an ExcelJS row into a plain array.
|
|
778
|
+
* ExcelJS row.values is 1-based (index 0 is undefined), so we normalize.
|
|
779
|
+
*/
|
|
780
|
+
private _excelRowToArray(row: any, colCount: number): any[] {
|
|
781
|
+
const values: any[] = [];
|
|
782
|
+
for (let c = 1; c <= colCount; c++) {
|
|
783
|
+
const cell = row.getCell(c);
|
|
784
|
+
if (!cell || cell.value === null || cell.value === undefined) {
|
|
785
|
+
values.push(null);
|
|
786
|
+
} else if (cell.type === 8 /* FormulaType */ && cell.result !== undefined) {
|
|
787
|
+
values.push(cell.result);
|
|
788
|
+
} else if (typeof cell.value === 'object' && cell.value !== null) {
|
|
789
|
+
// Handle rich text, hyperlinks, etc.
|
|
790
|
+
if (cell.value.richText) {
|
|
791
|
+
values.push(cell.value.richText.map((rt: any) => rt.text).join(''));
|
|
792
|
+
} else if (cell.value.text) {
|
|
793
|
+
values.push(cell.value.text);
|
|
794
|
+
} else if (cell.value.hyperlink) {
|
|
795
|
+
values.push(cell.value.text || cell.value.hyperlink);
|
|
796
|
+
} else {
|
|
797
|
+
values.push(String(cell.value));
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
values.push(cell.value);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return values;
|
|
804
|
+
}
|
|
805
|
+
|
|
795
806
|
/* ═══════════════════════════════════════════════════
|
|
796
807
|
* INTERNAL PARSING HELPERS
|
|
797
808
|
* ═══════════════════════════════════════════════════ */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.209",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "index.js",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"axios": "^1.6.0",
|
|
59
59
|
"chart.js": "^4.5.1",
|
|
60
60
|
"esbuild": "^0.19.0",
|
|
61
|
+
"exceljs": "^4.4.0",
|
|
61
62
|
"express": "^4.18.2",
|
|
62
|
-
"ws": "^8.13.0"
|
|
63
|
-
"xlsx": "^0.18.5"
|
|
63
|
+
"ws": "^8.13.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/express": "^4.17.17",
|