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.
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +16 -44
- package/lib/components/dataframe.ts +17 -43
- package/lib/storage/TabularDriver.d.ts +18 -7
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +157 -111
- package/lib/storage/TabularDriver.ts +159 -114
- package/package.json +3 -3
|
@@ -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
|
-
//
|
|
506
|
-
const
|
|
507
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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 = `
|
|
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 = `
|
|
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 = `
|
|
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 = `
|
|
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
|
-
//
|
|
621
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
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 = `
|
|
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 = `
|
|
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 = `
|
|
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 = `
|
|
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
|
|
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
|
/**
|
|
@@ -88,13 +88,24 @@ export declare class TabularDriver {
|
|
|
88
88
|
*/
|
|
89
89
|
fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
|
|
90
90
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
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":"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":"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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
|
|
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
|
|
@@ -487,117 +495,120 @@ export class TabularDriver {
|
|
|
487
495
|
return df;
|
|
488
496
|
}
|
|
489
497
|
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
|
|
494
|
-
|
|
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,
|
|
498
|
-
let
|
|
540
|
+
const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
|
|
541
|
+
let ExcelJS;
|
|
499
542
|
try {
|
|
500
|
-
|
|
543
|
+
ExcelJS = await import('exceljs');
|
|
501
544
|
}
|
|
502
545
|
catch {
|
|
503
|
-
throw new Error('XLSX support requires the "
|
|
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 =
|
|
511
|
-
|
|
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.
|
|
556
|
+
const totalSheets = workbook.worksheets.length;
|
|
517
557
|
let processedSheets = 0;
|
|
518
|
-
for (const
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
//
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
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}
|
|
581
|
-
//
|
|
582
|
-
for (let debugR = Math.max(
|
|
583
|
-
|
|
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
|
-
|
|
591
|
-
|
|
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
|
|
607
|
+
const rowObj = {};
|
|
597
608
|
for (let j = 0; j < headers.length; j++) {
|
|
598
|
-
|
|
609
|
+
rowObj[headers[j]] = rowData[j];
|
|
599
610
|
}
|
|
600
|
-
rows.push(
|
|
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
|
|
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
|
-
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
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
|
|
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
|
/* ═══════════════════════════════════════════════════
|
|
@@ -607,20 +614,60 @@ export class TabularDriver {
|
|
|
607
614
|
}
|
|
608
615
|
|
|
609
616
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
|
|
614
|
-
|
|
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,
|
|
664
|
+
const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
|
|
618
665
|
|
|
619
|
-
let
|
|
666
|
+
let ExcelJS: any;
|
|
620
667
|
try {
|
|
621
|
-
|
|
668
|
+
ExcelJS = await import('exceljs');
|
|
622
669
|
} catch {
|
|
623
|
-
throw new Error('XLSX support requires the "
|
|
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 =
|
|
633
|
-
|
|
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.
|
|
683
|
+
const totalSheets = workbook.worksheets.length;
|
|
640
684
|
let processedSheets = 0;
|
|
641
685
|
|
|
642
|
-
for (const
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
const
|
|
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 (
|
|
691
|
+
if (rowCount === 0 || colCount === 0) {
|
|
658
692
|
processedSheets++;
|
|
659
693
|
continue;
|
|
660
694
|
}
|
|
661
695
|
|
|
662
|
-
//
|
|
663
|
-
const
|
|
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}":
|
|
688
|
-
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)`);
|
|
689
701
|
|
|
690
|
-
if (
|
|
691
|
-
console.warn(`[TabularDriver] headerRow ${headerRow}
|
|
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
|
|
697
|
-
const
|
|
698
|
-
|
|
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}
|
|
714
|
-
//
|
|
715
|
-
for (let debugR = Math.max(
|
|
716
|
-
|
|
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
|
-
|
|
725
|
-
|
|
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
|
|
748
|
+
const rowObj: Record<string, any> = {};
|
|
734
749
|
for (let j = 0; j < headers.length; j++) {
|
|
735
|
-
|
|
750
|
+
rowObj[headers[j]] = rowData[j];
|
|
736
751
|
}
|
|
737
|
-
rows.push(
|
|
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.
|
|
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",
|