juxscript 1.1.206 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
@@ -502,45 +502,15 @@ export class DataFrameComponent extends BaseComponent {
502
502
  if (!this._rawFileData?.file)
503
503
  return;
504
504
  this._cleanupReshapeModal();
505
- // Read raw cells from the file
506
- const XLSX = await import('xlsx');
507
- const buffer = await this._rawFileData.file.arrayBuffer();
508
- const workbook = XLSX.read(buffer, {
509
- type: 'array',
510
- sheetRows: 20,
511
- dense: false
512
- });
513
- const sheetName = workbook.SheetNames[0];
514
- const worksheet = workbook.Sheets[sheetName];
515
- const ref = worksheet['!ref'];
516
- if (!ref)
505
+ // Use the SAME cell-reading method as the parser
506
+ const rawRows = await this._driver.readRawExcelRows(this._rawFileData.file, 15);
507
+ if (rawRows.length === 0)
517
508
  return;
518
- const range = XLSX.utils.decode_range(ref);
519
- const endRow = Math.min(range.e.r, 14); // Show up to 15 rows
520
- const startCol = range.s.c;
521
- const endCol = range.e.c;
522
- const readCellValue = (r, c) => {
523
- const addr = XLSX.utils.encode_cell({ r, c });
524
- const cell = worksheet[addr];
525
- if (!cell)
526
- return null;
527
- if (cell.w !== undefined)
528
- return cell.w;
529
- if (cell.v !== undefined)
530
- return cell.v;
531
- return null;
532
- };
533
- // Build raw row data with actual sheet row indices
534
- const rawRows = [];
535
- for (let r = 0; r <= endRow; r++) {
536
- const values = [];
537
- for (let c = startCol; c <= endCol; c++) {
538
- values.push(readCellValue(r, c));
539
- }
540
- rawRows.push({ sheetRow: r, values });
541
- }
509
+ // Log what we got so we can verify alignment
510
+ console.log('[DataFrame Preview] Raw rows from readRawExcelRows:');
511
+ rawRows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
542
512
  // Auto-detect best header row
543
- let selectedSheetRow = 0;
513
+ let selectedSheetRow = rawRows[0].sheetRow;
544
514
  for (const { sheetRow, values } of rawRows) {
545
515
  const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
546
516
  if (nonEmpty.length < values.length * 0.5)
@@ -584,6 +554,7 @@ export class DataFrameComponent extends BaseComponent {
584
554
  click: async () => {
585
555
  const input = document.getElementById(`${this._id}-header-row`);
586
556
  const headerRow = parseInt(input.value) || 0;
557
+ console.log(`[DataFrame] Apply clicked: headerRow=${headerRow}`);
587
558
  this.state.loading = true;
588
559
  this._updateStatus('Re-parsing with new settings...', 'loading');
589
560
  try {
@@ -626,22 +597,22 @@ export class DataFrameComponent extends BaseComponent {
626
597
  const vals = rawRows.find(r => r.sheetRow === row)?.values ?? [];
627
598
  const headerNames = vals.filter((v) => v != null && String(v).trim() !== '').map((v) => String(v).trim());
628
599
  const preview = headerNames.slice(0, 4).join(', ') + (headerNames.length > 4 ? '…' : '');
629
- if (row > 0) {
630
- hintDiv.innerHTML = `Row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows 0–${row - 1} will be skipped.`;
600
+ if (row > rawRows[0].sheetRow) {
601
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows before it will be skipped.`;
631
602
  }
632
603
  else {
633
- hintDiv.innerHTML = `Row <strong>0</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
604
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
634
605
  }
635
606
  };
636
607
  const renderPreview = (selected) => {
637
608
  if (!previewDiv)
638
609
  return;
639
610
  previewDiv.innerHTML = this._buildClickablePreviewHTML(rawRows, selected);
640
- // Wire click handlers on each row
641
611
  previewDiv.querySelectorAll('tr[data-sheet-row]').forEach(tr => {
642
612
  tr.addEventListener('click', () => {
643
613
  const rowIdx = parseInt(tr.dataset.sheetRow);
644
614
  hiddenInput.value = String(rowIdx);
615
+ console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
645
616
  updateHint(rowIdx);
646
617
  renderPreview(rowIdx);
647
618
  });
@@ -759,11 +730,11 @@ export class DataFrameComponent extends BaseComponent {
759
730
  const vals = rawRows.find(r => r.sheetRow === row)?.values ?? [];
760
731
  const headerNames = vals.filter((v) => v != null && String(v).trim() !== '').map((v) => String(v).trim());
761
732
  const preview = headerNames.slice(0, 4).join(', ') + (headerNames.length > 4 ? '…' : '');
762
- if (row > 0) {
763
- hintDiv.innerHTML = `Row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows 0–${row - 1} will be skipped.`;
733
+ if (row > rawRows[0].sheetRow) {
734
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows before it will be skipped.`;
764
735
  }
765
736
  else {
766
- hintDiv.innerHTML = `Row <strong>0</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
737
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
767
738
  }
768
739
  };
769
740
  const reparse = () => {
@@ -786,6 +757,7 @@ export class DataFrameComponent extends BaseComponent {
786
757
  tr.addEventListener('click', () => {
787
758
  const rowIdx = parseInt(tr.dataset.sheetRow);
788
759
  hiddenInput.value = String(rowIdx);
760
+ console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
789
761
  updateHint(rowIdx);
790
762
  renderPreview(rowIdx);
791
763
  });
@@ -617,46 +617,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
617
617
 
618
618
  this._cleanupReshapeModal();
619
619
 
620
- // Read raw cells from the file
621
- const XLSX = await import('xlsx');
622
- const buffer = await this._rawFileData.file.arrayBuffer();
623
- const workbook = XLSX.read(buffer, {
624
- type: 'array',
625
- sheetRows: 20,
626
- dense: false
627
- });
620
+ // Use the SAME cell-reading method as the parser
621
+ const rawRows = await this._driver.readRawExcelRows(this._rawFileData.file, 15);
628
622
 
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 endRow = Math.min(range.e.r, 14); // Show up to 15 rows
636
- const startCol = range.s.c;
637
- const endCol = range.e.c;
638
-
639
- const readCellValue = (r: number, c: number): any => {
640
- const addr = XLSX.utils.encode_cell({ r, c });
641
- const cell = worksheet[addr];
642
- if (!cell) return null;
643
- if (cell.w !== undefined) return cell.w;
644
- if (cell.v !== undefined) return cell.v;
645
- return null;
646
- };
623
+ if (rawRows.length === 0) return;
647
624
 
648
- // Build raw row data with actual sheet row indices
649
- const rawRows: { sheetRow: number; values: any[] }[] = [];
650
- for (let r = 0; r <= endRow; r++) {
651
- const values: any[] = [];
652
- for (let c = startCol; c <= endCol; c++) {
653
- values.push(readCellValue(r, c));
654
- }
655
- rawRows.push({ sheetRow: r, values });
656
- }
625
+ // Log what we got so we can verify alignment
626
+ console.log('[DataFrame Preview] Raw rows from readRawExcelRows:');
627
+ rawRows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
657
628
 
658
629
  // Auto-detect best header row
659
- let selectedSheetRow = 0;
630
+ let selectedSheetRow = rawRows[0].sheetRow;
660
631
  for (const { sheetRow, values } of rawRows) {
661
632
  const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
662
633
  if (nonEmpty.length < values.length * 0.5) continue;
@@ -703,6 +674,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
703
674
  const input = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
704
675
  const headerRow = parseInt(input.value) || 0;
705
676
 
677
+ console.log(`[DataFrame] Apply clicked: headerRow=${headerRow}`);
678
+
706
679
  this.state.loading = true;
707
680
  this._updateStatus('Re-parsing with new settings...', 'loading');
708
681
 
@@ -752,10 +725,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
752
725
  const vals = rawRows.find(r => r.sheetRow === row)?.values ?? [];
753
726
  const headerNames = vals.filter((v: any) => v != null && String(v).trim() !== '').map((v: any) => String(v).trim());
754
727
  const preview = headerNames.slice(0, 4).join(', ') + (headerNames.length > 4 ? '…' : '');
755
- if (row > 0) {
756
- hintDiv.innerHTML = `Row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows 0–${row - 1} will be skipped.`;
728
+ if (row > rawRows[0].sheetRow) {
729
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows before it will be skipped.`;
757
730
  } else {
758
- hintDiv.innerHTML = `Row <strong>0</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
731
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
759
732
  }
760
733
  };
761
734
 
@@ -763,11 +736,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
763
736
  if (!previewDiv) return;
764
737
  previewDiv.innerHTML = this._buildClickablePreviewHTML(rawRows, selected);
765
738
 
766
- // Wire click handlers on each row
767
739
  previewDiv.querySelectorAll('tr[data-sheet-row]').forEach(tr => {
768
740
  tr.addEventListener('click', () => {
769
741
  const rowIdx = parseInt((tr as HTMLElement).dataset.sheetRow!);
770
742
  hiddenInput.value = String(rowIdx);
743
+ console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
771
744
  updateHint(rowIdx);
772
745
  renderPreview(rowIdx);
773
746
  });
@@ -898,10 +871,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
898
871
  const vals = rawRows.find(r => r.sheetRow === row)?.values ?? [];
899
872
  const headerNames = vals.filter((v: any) => v != null && String(v).trim() !== '').map((v: any) => String(v).trim());
900
873
  const preview = headerNames.slice(0, 4).join(', ') + (headerNames.length > 4 ? '…' : '');
901
- if (row > 0) {
902
- hintDiv.innerHTML = `Row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows 0–${row - 1} will be skipped.`;
874
+ if (row > rawRows[0].sheetRow) {
875
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows before it will be skipped.`;
903
876
  } else {
904
- hintDiv.innerHTML = `Row <strong>0</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
877
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
905
878
  }
906
879
  };
907
880
 
@@ -926,6 +899,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
926
899
  tr.addEventListener('click', () => {
927
900
  const rowIdx = parseInt((tr as HTMLElement).dataset.sheetRow!);
928
901
  hiddenInput.value = String(rowIdx);
902
+ console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
929
903
  updateHint(rowIdx);
930
904
  renderPreview(rowIdx);
931
905
  });
@@ -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
  /**
@@ -88,13 +88,24 @@ export declare class TabularDriver {
88
88
  */
89
89
  fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
90
90
  /**
91
- * FIXED: Stream Excel file with optional headerRow override
92
- * headerRow is 0-based: 0 = first row, 1 = second row, etc.
93
- *
94
- * Uses direct cell access instead of sheet_to_json to avoid
95
- * issues with blank row handling and sparse arrays.
91
+ * Read raw cell values from first sheet of an Excel file.
92
+ * Returns rows with their 0-based row indices.
93
+ * Used by both the preview UI and the parser to ensure consistency.
94
+ */
95
+ readRawExcelRows(file: File, maxRows?: number): Promise<{
96
+ sheetRow: number;
97
+ values: any[];
98
+ }[]>;
99
+ /**
100
+ * Stream Excel file with optional headerRow override.
101
+ * headerRow is 0-based (same as sheetRow from readRawExcelRows).
96
102
  */
97
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;
98
109
  private _splitLines;
99
110
  private _parseLine;
100
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
@@ -487,117 +495,120 @@ export class TabularDriver {
487
495
  return df;
488
496
  }
489
497
  /**
490
- * FIXED: Stream Excel file with optional headerRow override
491
- * headerRow is 0-based: 0 = first row, 1 = second row, etc.
492
- *
493
- * Uses direct cell access instead of sheet_to_json to avoid
494
- * issues with blank row handling and sparse arrays.
498
+ * Read raw cell values from first sheet of an Excel file.
499
+ * Returns rows with their 0-based row indices.
500
+ * Used by both the preview UI and the parser to ensure consistency.
501
+ */
502
+ async readRawExcelRows(file, maxRows = 15) {
503
+ let ExcelJS;
504
+ try {
505
+ ExcelJS = await import('exceljs');
506
+ }
507
+ catch {
508
+ throw new Error('XLSX support requires the "exceljs" package.');
509
+ }
510
+ const buffer = await file.arrayBuffer();
511
+ const workbook = new ExcelJS.Workbook();
512
+ await workbook.xlsx.load(buffer);
513
+ const worksheet = workbook.worksheets[0];
514
+ if (!worksheet)
515
+ return [];
516
+ const colCount = worksheet.columnCount;
517
+ const rows = [];
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;
530
+ }
531
+ console.log(`[readRawExcelRows] ${rows.length} rows, colCount=${colCount}`);
532
+ rows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
533
+ return rows;
534
+ }
535
+ /**
536
+ * Stream Excel file with optional headerRow override.
537
+ * headerRow is 0-based (same as sheetRow from readRawExcelRows).
495
538
  */
496
539
  async streamFileMultiSheet(file, options = {}) {
497
- const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow = 0 } = options;
498
- let XLSX;
540
+ const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
541
+ let ExcelJS;
499
542
  try {
500
- XLSX = await import('xlsx');
543
+ ExcelJS = await import('exceljs');
501
544
  }
502
545
  catch {
503
- 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');
504
547
  }
505
548
  if (onProgress)
506
549
  onProgress(0, file.size);
507
550
  const buffer = await file.arrayBuffer();
508
551
  if (onProgress)
509
552
  onProgress(file.size * 0.3, file.size);
510
- const workbook = XLSX.read(buffer, {
511
- type: 'array',
512
- sheetRows: maxSheetSize,
513
- dense: false
514
- });
553
+ const workbook = new ExcelJS.Workbook();
554
+ await workbook.xlsx.load(buffer);
515
555
  const sheets = {};
516
- const totalSheets = workbook.SheetNames.length;
556
+ const totalSheets = workbook.worksheets.length;
517
557
  let processedSheets = 0;
518
- for (const sheetName of workbook.SheetNames) {
519
- const worksheet = workbook.Sheets[sheetName];
520
- const ref = worksheet['!ref'];
521
- if (!ref) {
522
- processedSheets++;
523
- continue;
524
- }
525
- const range = XLSX.utils.decode_range(ref);
526
- const startRow = range.s.r;
527
- const endRow = range.e.r;
528
- const startCol = range.s.c;
529
- const endCol = range.e.c;
530
- 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) {
531
563
  processedSheets++;
532
564
  continue;
533
565
  }
534
- // Direct cell reader - bypasses sheet_to_json completely
535
- const readCellValue = (r, c) => {
536
- const addr = XLSX.utils.encode_cell({ r, c });
537
- const cell = worksheet[addr];
538
- if (!cell)
539
- return null;
540
- if (cell.w !== undefined)
541
- return cell.w;
542
- if (cell.v !== undefined)
543
- return cell.v;
544
- return null;
545
- };
546
- const readRow = (r) => {
547
- const vals = [];
548
- for (let c = startCol; c <= endCol; c++) {
549
- vals.push(readCellValue(r, c));
550
- }
551
- return vals;
552
- };
553
- // ✅ FIX: headerRow is the user's 0-based index counting from
554
- // the first row of the file. Do NOT add startRow — the user
555
- // sees row 0 as the first row regardless of where the sheet
556
- // range begins. We simply use headerRow as the absolute sheet
557
- // row index.
558
- const headerSheetRow = headerRow;
559
- console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}, startCol=${startCol}, endCol=${endCol}`);
560
- console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow} (absolute, no startRow offset)`);
561
- if (headerSheetRow > endRow) {
562
- console.warn(`[TabularDriver] headerRow ${headerRow} (sheet row ${headerSheetRow}) 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}`);
563
572
  processedSheets++;
564
573
  continue;
565
574
  }
566
- // Read header values directly from cells
567
- const headerValues = readRow(headerSheetRow);
568
- console.log(`[TabularDriver] Raw header values at sheet row ${headerSheetRow}:`, headerValues);
569
- // Build headers array
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);
570
579
  const headers = headerValues.map((h, i) => {
571
580
  if (h === null || h === undefined || String(h).trim() === '') {
572
581
  return `__EMPTY${i > 0 ? '_' + i : ''}`;
573
582
  }
574
583
  return String(h).trim();
575
584
  });
576
- // Count valid headers
577
585
  const validHeaders = headers.filter(h => !h.startsWith('__EMPTY'));
578
586
  console.log(`[TabularDriver] Headers (${validHeaders.length} valid / ${headers.length} total):`, headers);
579
587
  if (validHeaders.length === 0) {
580
- console.warn(`[TabularDriver] No valid headers found at row ${headerRow} (sheet row ${headerSheetRow}) in sheet "${sheetName}"`);
581
- // Log surrounding rows for debugging
582
- for (let debugR = Math.max(startRow, headerSheetRow - 2); debugR <= Math.min(endRow, headerSheetRow + 2); debugR++) {
583
- console.log(`[TabularDriver] row ${debugR}:`, readRow(debugR));
588
+ console.warn(`[TabularDriver] No valid headers found at row ${headerRow} in sheet "${sheetName}"`);
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));
584
593
  }
585
594
  processedSheets++;
586
595
  continue;
587
596
  }
588
597
  // Build data rows: everything after the header row
589
598
  const rows = [];
590
- for (let r = headerSheetRow + 1; r <= endRow; r++) {
591
- const rowData = readRow(r);
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);
592
603
  // Skip completely empty rows
593
- const hasContent = rowData.some(cell => cell !== null && cell !== undefined && String(cell).trim() !== '');
604
+ const hasContent = rowData.some((cell) => cell !== null && cell !== undefined && String(cell).trim() !== '');
594
605
  if (!hasContent)
595
606
  continue;
596
- const row = {};
607
+ const rowObj = {};
597
608
  for (let j = 0; j < headers.length; j++) {
598
- row[headers[j]] = rowData[j];
609
+ rowObj[headers[j]] = rowData[j];
599
610
  }
600
- rows.push(row);
611
+ rows.push(rowObj);
601
612
  }
602
613
  console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
603
614
  if (rows.length > 0) {
@@ -616,6 +627,41 @@ export class TabularDriver {
616
627
  onProgress(file.size, file.size);
617
628
  return sheets;
618
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
+ }
619
665
  /* ═══════════════════════════════════════════════════
620
666
  * INTERNAL PARSING HELPERS
621
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);
355
346
 
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));
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;
361
353
  }
362
- return typed;
354
+
355
+ if (!headers) {
356
+ headers = values.map((_: any, i: number) => `col_${i}`);
357
+ }
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
  /* ═══════════════════════════════════════════════════
@@ -607,20 +614,60 @@ export class TabularDriver {
607
614
  }
608
615
 
609
616
  /**
610
- * FIXED: Stream Excel file with optional headerRow override
611
- * headerRow is 0-based: 0 = first row, 1 = second row, etc.
612
- *
613
- * Uses direct cell access instead of sheet_to_json to avoid
614
- * issues with blank row handling and sparse arrays.
617
+ * Read raw cell values from first sheet of an Excel file.
618
+ * Returns rows with their 0-based row indices.
619
+ * Used by both the preview UI and the parser to ensure consistency.
620
+ */
621
+ async readRawExcelRows(file: File, maxRows: number = 15): Promise<{ sheetRow: number; values: any[] }[]> {
622
+ let ExcelJS: any;
623
+ try {
624
+ ExcelJS = await import('exceljs');
625
+ } catch {
626
+ throw new Error('XLSX support requires the "exceljs" package.');
627
+ }
628
+
629
+ const buffer = await file.arrayBuffer();
630
+ const workbook = new ExcelJS.Workbook();
631
+ await workbook.xlsx.load(buffer);
632
+
633
+ const worksheet = workbook.worksheets[0];
634
+ if (!worksheet) return [];
635
+
636
+ const colCount = worksheet.columnCount;
637
+ const rows: { sheetRow: number; values: any[] }[] = [];
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;
651
+ }
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
+ return rows;
657
+ }
658
+
659
+ /**
660
+ * Stream Excel file with optional headerRow override.
661
+ * headerRow is 0-based (same as sheetRow from readRawExcelRows).
615
662
  */
616
663
  async streamFileMultiSheet(file: File, options: ParseOptions = {}): Promise<Record<string, DataFrame>> {
617
- const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow = 0 } = options;
664
+ const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
618
665
 
619
- let XLSX: any;
666
+ let ExcelJS: any;
620
667
  try {
621
- XLSX = await import('xlsx');
668
+ ExcelJS = await import('exceljs');
622
669
  } catch {
623
- 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');
624
671
  }
625
672
 
626
673
  if (onProgress) onProgress(0, file.size);
@@ -629,75 +676,40 @@ export class TabularDriver {
629
676
 
630
677
  if (onProgress) onProgress(file.size * 0.3, file.size);
631
678
 
632
- const workbook = XLSX.read(buffer, {
633
- type: 'array',
634
- sheetRows: maxSheetSize,
635
- dense: false
636
- });
679
+ const workbook = new ExcelJS.Workbook();
680
+ await workbook.xlsx.load(buffer);
637
681
 
638
682
  const sheets: Record<string, DataFrame> = {};
639
- const totalSheets = workbook.SheetNames.length;
683
+ const totalSheets = workbook.worksheets.length;
640
684
  let processedSheets = 0;
641
685
 
642
- for (const sheetName of workbook.SheetNames) {
643
- const worksheet = workbook.Sheets[sheetName];
644
-
645
- const ref = worksheet['!ref'];
646
- if (!ref) {
647
- processedSheets++;
648
- continue;
649
- }
650
-
651
- const range = XLSX.utils.decode_range(ref);
652
- const startRow = range.s.r;
653
- const endRow = range.e.r;
654
- const startCol = range.s.c;
655
- 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;
656
690
 
657
- if (endRow < startRow) {
691
+ if (rowCount === 0 || colCount === 0) {
658
692
  processedSheets++;
659
693
  continue;
660
694
  }
661
695
 
662
- // Direct cell reader - bypasses sheet_to_json completely
663
- const readCellValue = (r: number, c: number): any => {
664
- const addr = XLSX.utils.encode_cell({ r, c });
665
- const cell = worksheet[addr];
666
- if (!cell) return null;
667
- if (cell.w !== undefined) return cell.w;
668
- if (cell.v !== undefined) return cell.v;
669
- return null;
670
- };
671
-
672
- const readRow = (r: number): any[] => {
673
- const vals: any[] = [];
674
- for (let c = startCol; c <= endCol; c++) {
675
- vals.push(readCellValue(r, c));
676
- }
677
- return vals;
678
- };
679
-
680
- // ✅ FIX: headerRow is the user's 0-based index counting from
681
- // the first row of the file. Do NOT add startRow — the user
682
- // sees row 0 as the first row regardless of where the sheet
683
- // range begins. We simply use headerRow as the absolute sheet
684
- // row index.
685
- const headerSheetRow = headerRow;
696
+ // headerRow is 0-based; ExcelJS rows are 1-based
697
+ const headerExcelRow = headerRow + 1;
686
698
 
687
- console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}, startCol=${startCol}, endCol=${endCol}`);
688
- console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow} (absolute, no startRow offset)`);
699
+ console.log(`[TabularDriver] Sheet "${sheetName}": rowCount=${rowCount}, colCount=${colCount}`);
700
+ console.log(`[TabularDriver] headerRow=${headerRow} (0-based), excelRow=${headerExcelRow} (1-based)`);
689
701
 
690
- if (headerSheetRow > endRow) {
691
- console.warn(`[TabularDriver] headerRow ${headerRow} (sheet row ${headerSheetRow}) exceeds endRow ${endRow}`);
702
+ if (headerExcelRow > rowCount) {
703
+ console.warn(`[TabularDriver] headerRow ${headerRow} exceeds rowCount ${rowCount}`);
692
704
  processedSheets++;
693
705
  continue;
694
706
  }
695
707
 
696
- // Read header values directly from cells
697
- const headerValues = readRow(headerSheetRow);
698
- 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);
699
712
 
700
- // Build headers array
701
713
  const headers: string[] = headerValues.map((h: any, i: number) => {
702
714
  if (h === null || h === undefined || String(h).trim() === '') {
703
715
  return `__EMPTY${i > 0 ? '_' + i : ''}`;
@@ -705,15 +717,15 @@ export class TabularDriver {
705
717
  return String(h).trim();
706
718
  });
707
719
 
708
- // Count valid headers
709
720
  const validHeaders = headers.filter(h => !h.startsWith('__EMPTY'));
710
721
  console.log(`[TabularDriver] Headers (${validHeaders.length} valid / ${headers.length} total):`, headers);
711
722
 
712
723
  if (validHeaders.length === 0) {
713
- console.warn(`[TabularDriver] No valid headers found at row ${headerRow} (sheet row ${headerSheetRow}) in sheet "${sheetName}"`);
714
- // Log surrounding rows for debugging
715
- for (let debugR = Math.max(startRow, headerSheetRow - 2); debugR <= Math.min(endRow, headerSheetRow + 2); debugR++) {
716
- console.log(`[TabularDriver] row ${debugR}:`, readRow(debugR));
724
+ console.warn(`[TabularDriver] No valid headers found at row ${headerRow} in sheet "${sheetName}"`);
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));
717
729
  }
718
730
  processedSheets++;
719
731
  continue;
@@ -721,20 +733,23 @@ export class TabularDriver {
721
733
 
722
734
  // Build data rows: everything after the header row
723
735
  const rows: Record<string, any>[] = [];
724
- for (let r = headerSheetRow + 1; r <= endRow; r++) {
725
- 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);
726
741
 
727
742
  // Skip completely empty rows
728
- const hasContent = rowData.some(cell =>
743
+ const hasContent = rowData.some((cell: any) =>
729
744
  cell !== null && cell !== undefined && String(cell).trim() !== ''
730
745
  );
731
746
  if (!hasContent) continue;
732
747
 
733
- const row: Record<string, any> = {};
748
+ const rowObj: Record<string, any> = {};
734
749
  for (let j = 0; j < headers.length; j++) {
735
- row[headers[j]] = rowData[j];
750
+ rowObj[headers[j]] = rowData[j];
736
751
  }
737
- rows.push(row);
752
+ rows.push(rowObj);
738
753
  }
739
754
 
740
755
  console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
@@ -758,6 +773,36 @@ export class TabularDriver {
758
773
  return sheets;
759
774
  }
760
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
+
761
806
  /* ═══════════════════════════════════════════════════
762
807
  * INTERNAL PARSING HELPERS
763
808
  * ═══════════════════════════════════════════════════ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.206",
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",