juxscript 1.1.207 → 1.1.208

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.
@@ -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 SheetJS
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 (useful for UI)
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 actual sheet row indices.
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
- * ✅ FIXED: Stream Excel file with optional headerRow override
101
- * headerRow is the absolute sheet row index (same as sheetRow from readRawExcelRows).
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":"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"}
1
+ {"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
@@ -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 SheetJS
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
- // Dynamic import — fails gracefully if xlsx not installed
221
- let XLSX;
220
+ let ExcelJS;
222
221
  try {
223
- XLSX = await import('xlsx');
222
+ ExcelJS = await import('exceljs');
224
223
  }
225
224
  catch {
226
- throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
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 = XLSX.read(buffer, { type: 'array' });
232
+ const workbook = new ExcelJS.Workbook();
233
+ await workbook.xlsx.load(buffer);
234
234
  // Select sheet
235
- let sheetName;
235
+ let worksheet;
236
236
  if (typeof sheet === 'number') {
237
- sheetName = workbook.SheetNames[sheet] || workbook.SheetNames[0];
237
+ worksheet = workbook.worksheets[sheet] || workbook.worksheets[0];
238
238
  }
239
239
  else if (typeof sheet === 'string') {
240
- sheetName = sheet;
240
+ worksheet = workbook.getWorksheet(sheet) || workbook.worksheets[0];
241
241
  }
242
242
  else {
243
- sheetName = workbook.SheetNames[0];
243
+ worksheet = workbook.worksheets[0];
244
244
  }
245
- const worksheet = workbook.Sheets[sheetName];
246
245
  if (!worksheet) {
247
- throw new Error(`Sheet "${sheetName}" not found. Available: ${workbook.SheetNames.join(', ')}`);
246
+ const names = workbook.worksheets.map((ws) => ws.name).join(', ');
247
+ throw new Error(`No worksheet found. Available: ${names}`);
248
248
  }
249
- // Convert to JSON rows
250
- const jsonRows = XLSX.utils.sheet_to_json(worksheet, {
251
- header: hasHeader ? undefined : 1,
252
- defval: null,
253
- raw: true
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.8, file.size);
257
- // Apply skipRows and maxRows
258
- let rows = jsonRows;
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 (useful for UI)
288
+ * Get sheet names from an XLSX file
282
289
  */
283
290
  async getSheetNames(file) {
284
- let XLSX;
291
+ let ExcelJS;
285
292
  try {
286
- XLSX = await import('xlsx');
293
+ ExcelJS = await import('exceljs');
287
294
  }
288
295
  catch {
289
- throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
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 = XLSX.read(buffer, { type: 'array' });
293
- return workbook.SheetNames;
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 actual sheet row indices.
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 XLSX;
503
+ let ExcelJS;
496
504
  try {
497
- XLSX = await import('xlsx');
505
+ ExcelJS = await import('exceljs');
498
506
  }
499
507
  catch {
500
- throw new Error('XLSX support requires the "xlsx" package.');
508
+ throw new Error('XLSX support requires the "exceljs" package.');
501
509
  }
502
510
  const buffer = await file.arrayBuffer();
503
- const workbook = XLSX.read(buffer, {
504
- type: 'array',
505
- sheetRows: maxRows + 5,
506
- dense: false
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 range = XLSX.utils.decode_range(ref);
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
- for (let r = startRow; r <= endRow; r++) {
521
- const values = [];
522
- for (let c = startCol; c <= endCol; c++) {
523
- const addr = XLSX.utils.encode_cell({ r, c });
524
- const cell = worksheet[addr];
525
- if (!cell) {
526
- values.push(null);
527
- continue;
528
- }
529
- if (cell.w !== undefined) {
530
- values.push(cell.w);
531
- continue;
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
- * ✅ FIXED: Stream Excel file with optional headerRow override
545
- * headerRow is the absolute sheet row index (same as sheetRow from readRawExcelRows).
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, sheetChunkSize = 10000, onProgress, headerRow = 0 } = options;
549
- let XLSX;
540
+ const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
541
+ let ExcelJS;
550
542
  try {
551
- XLSX = await import('xlsx');
543
+ ExcelJS = await import('exceljs');
552
544
  }
553
545
  catch {
554
- throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
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 = XLSX.read(buffer, {
562
- type: 'array',
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.SheetNames.length;
556
+ const totalSheets = workbook.worksheets.length;
568
557
  let processedSheets = 0;
569
- for (const sheetName of workbook.SheetNames) {
570
- const worksheet = workbook.Sheets[sheetName];
571
- const ref = worksheet['!ref'];
572
- if (!ref) {
573
- processedSheets++;
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
- const readCellValue = (r, c) => {
586
- const addr = XLSX.utils.encode_cell({ r, c });
587
- const cell = worksheet[addr];
588
- if (!cell)
589
- return null;
590
- if (cell.w !== undefined)
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
- const headerValues = readRow(headerSheetRow);
614
- console.log(`[TabularDriver] Raw header values at sheet row ${headerSheetRow}:`, headerValues);
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
- for (let debugR = Math.max(startRow, headerSheetRow - 2); debugR <= Math.min(endRow, headerSheetRow + 2); debugR++) {
626
- console.log(`[TabularDriver] row ${debugR}:`, readRow(debugR));
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
- for (let r = headerSheetRow + 1; r <= endRow; r++) {
633
- const rowData = readRow(r);
634
- const hasContent = rowData.some(cell => cell !== null && cell !== undefined && String(cell).trim() !== '');
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 row = {};
607
+ const rowObj = {};
638
608
  for (let j = 0; j < headers.length; j++) {
639
- row[headers[j]] = rowData[j];
609
+ rowObj[headers[j]] = rowData[j];
640
610
  }
641
- rows.push(row);
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 SheetJS
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
- // Dynamic import — fails gracefully if xlsx not installed
306
- let XLSX: any;
305
+ let ExcelJS: any;
307
306
  try {
308
- XLSX = await import('xlsx');
307
+ ExcelJS = await import('exceljs');
309
308
  } catch {
310
309
  throw new Error(
311
- 'XLSX support requires the "xlsx" package. Install it with: npm install xlsx'
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 = XLSX.read(buffer, { type: 'array' });
320
+ const workbook = new ExcelJS.Workbook();
321
+ await workbook.xlsx.load(buffer);
322
322
 
323
323
  // Select sheet
324
- let sheetName: string;
324
+ let worksheet: any;
325
325
  if (typeof sheet === 'number') {
326
- sheetName = workbook.SheetNames[sheet] || workbook.SheetNames[0];
326
+ worksheet = workbook.worksheets[sheet] || workbook.worksheets[0];
327
327
  } else if (typeof sheet === 'string') {
328
- sheetName = sheet;
328
+ worksheet = workbook.getWorksheet(sheet) || workbook.worksheets[0];
329
329
  } else {
330
- sheetName = workbook.SheetNames[0];
330
+ worksheet = workbook.worksheets[0];
331
331
  }
332
332
 
333
- const worksheet = workbook.Sheets[sheetName];
334
333
  if (!worksheet) {
335
- throw new Error(`Sheet "${sheetName}" not found. Available: ${workbook.SheetNames.join(', ')}`);
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
- // Convert to JSON rows
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
- if (onProgress) onProgress(file.size * 0.8, file.size);
340
+ // Read all rows
341
+ const allRows: Record<string, any>[] = [];
342
+ let headers: string[] | null = null;
346
343
 
347
- // Apply skipRows and maxRows
348
- let rows = jsonRows;
349
- if (skipRows > 0) {
350
- rows = rows.slice(skipRows);
351
- }
352
- if (maxRows !== undefined) {
353
- rows = rows.slice(0, maxRows);
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
- // Auto-type values
357
- rows = rows.map(row => {
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
- return typed;
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 (useful for UI)
380
+ * Get sheet names from an XLSX file
375
381
  */
376
382
  async getSheetNames(file: File): Promise<string[]> {
377
- let XLSX: any;
383
+ let ExcelJS: any;
378
384
  try {
379
- XLSX = await import('xlsx');
385
+ ExcelJS = await import('exceljs');
380
386
  } catch {
381
- throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
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 = XLSX.read(buffer, { type: 'array' });
386
- return workbook.SheetNames;
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 actual sheet row indices.
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 XLSX: any;
622
+ let ExcelJS: any;
616
623
  try {
617
- XLSX = await import('xlsx');
624
+ ExcelJS = await import('exceljs');
618
625
  } catch {
619
- throw new Error('XLSX support requires the "xlsx" package.');
626
+ throw new Error('XLSX support requires the "exceljs" package.');
620
627
  }
621
628
 
622
629
  const buffer = await file.arrayBuffer();
623
- const workbook = XLSX.read(buffer, {
624
- type: 'array',
625
- sheetRows: maxRows + 5,
626
- dense: false
627
- });
630
+ const workbook = new ExcelJS.Workbook();
631
+ await workbook.xlsx.load(buffer);
628
632
 
629
- const sheetName = workbook.SheetNames[0];
630
- const worksheet = workbook.Sheets[sheetName];
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
- for (let r = startRow; r <= endRow; r++) {
644
- const values: any[] = [];
645
- for (let c = startCol; c <= endCol; c++) {
646
- const addr = XLSX.utils.encode_cell({ r, c });
647
- const cell = worksheet[addr];
648
- if (!cell) { values.push(null); continue; }
649
- if (cell.w !== undefined) { values.push(cell.w); continue; }
650
- if (cell.v !== undefined) { values.push(cell.v); continue; }
651
- values.push(null);
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
- * ✅ FIXED: Stream Excel file with optional headerRow override
661
- * headerRow is the absolute sheet row index (same as sheetRow from readRawExcelRows).
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, sheetChunkSize = 10000, onProgress, headerRow = 0 } = options;
664
+ const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
665
665
 
666
- let XLSX: any;
666
+ let ExcelJS: any;
667
667
  try {
668
- XLSX = await import('xlsx');
668
+ ExcelJS = await import('exceljs');
669
669
  } catch {
670
- throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
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 = XLSX.read(buffer, {
680
- type: 'array',
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.SheetNames.length;
683
+ const totalSheets = workbook.worksheets.length;
687
684
  let processedSheets = 0;
688
685
 
689
- for (const sheetName of workbook.SheetNames) {
690
- const worksheet = workbook.Sheets[sheetName];
691
-
692
- const ref = worksheet['!ref'];
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 (endRow < startRow) {
691
+ if (rowCount === 0 || colCount === 0) {
705
692
  processedSheets++;
706
693
  continue;
707
694
  }
708
695
 
709
- const readCellValue = (r: number, c: number): any => {
710
- const addr = XLSX.utils.encode_cell({ r, c });
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}": range=${ref}, startRow=${startRow}, endRow=${endRow}`);
731
- console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow}`);
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 (headerSheetRow > endRow) {
734
- console.warn(`[TabularDriver] headerRow ${headerRow} exceeds endRow ${endRow}`);
702
+ if (headerExcelRow > rowCount) {
703
+ console.warn(`[TabularDriver] headerRow ${headerRow} exceeds rowCount ${rowCount}`);
735
704
  processedSheets++;
736
705
  continue;
737
706
  }
738
707
 
739
- const headerValues = readRow(headerSheetRow);
740
- console.log(`[TabularDriver] Raw header values at sheet row ${headerSheetRow}:`, headerValues);
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
- for (let debugR = Math.max(startRow, headerSheetRow - 2); debugR <= Math.min(endRow, headerSheetRow + 2); debugR++) {
755
- console.log(`[TabularDriver] row ${debugR}:`, readRow(debugR));
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
- for (let r = headerSheetRow + 1; r <= endRow; r++) {
763
- const rowData = readRow(r);
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
- const hasContent = rowData.some(cell =>
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 row: Record<string, any> = {};
748
+ const rowObj: Record<string, any> = {};
771
749
  for (let j = 0; j < headers.length; j++) {
772
- row[headers[j]] = rowData[j];
750
+ rowObj[headers[j]] = rowData[j];
773
751
  }
774
- rows.push(row);
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.207",
3
+ "version": "1.1.208",
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",