juxscript 1.1.181 → 1.1.182
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 +10 -0
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +40 -4
- package/lib/components/dataframe.ts +48 -4
- package/lib/storage/TabularDriver.d.ts +12 -0
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +73 -5
- package/lib/storage/TabularDriver.ts +89 -7
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@ export interface DataFrameOptions {
|
|
|
16
16
|
icon?: string;
|
|
17
17
|
maxSheetSize?: number;
|
|
18
18
|
sheetChunkSize?: number;
|
|
19
|
+
maxFileSize?: number;
|
|
20
|
+
showReshapeWarning?: boolean;
|
|
19
21
|
style?: string;
|
|
20
22
|
class?: string;
|
|
21
23
|
}
|
|
@@ -40,6 +42,9 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
40
42
|
private _icon;
|
|
41
43
|
private _maxSheetSize;
|
|
42
44
|
private _sheetChunkSize;
|
|
45
|
+
private _maxFileSize;
|
|
46
|
+
private _showReshapeWarning;
|
|
47
|
+
private _rawFileData;
|
|
43
48
|
constructor(id: string, options?: DataFrameOptions);
|
|
44
49
|
protected getTriggerEvents(): readonly string[];
|
|
45
50
|
protected getCallbackEvents(): readonly string[];
|
|
@@ -80,6 +85,10 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
80
85
|
* ✅ NEW: Set chunk size for processing large sheets
|
|
81
86
|
*/
|
|
82
87
|
sheetChunkSize(v: number): this;
|
|
88
|
+
/**
|
|
89
|
+
* ✅ NEW: Set max file size in MB
|
|
90
|
+
*/
|
|
91
|
+
maxFileSize(mb: number): this;
|
|
83
92
|
/**
|
|
84
93
|
* ✅ FIXED: Render multiple Excel sheets as tabs
|
|
85
94
|
*/
|
|
@@ -88,6 +97,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
88
97
|
private _setDataFrame;
|
|
89
98
|
private _updateTable;
|
|
90
99
|
private _showFilterInput;
|
|
100
|
+
private _showReshapeModal;
|
|
91
101
|
update(prop: string, value: any): void;
|
|
92
102
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
93
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAOnC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,cAAc,GAAG,SAAS,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,aAAa,CAAC,cAAc,CAAC;IACjE,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,aAAa,CAOnB;IACF,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAAgE;IACrF,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,eAAe,CAAiB;
|
|
1
|
+
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,9 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
32
32
|
this._icon = '';
|
|
33
33
|
this._maxSheetSize = 100000; // ✅ Default 100k rows
|
|
34
34
|
this._sheetChunkSize = 10000; // ✅ Default 10k chunk
|
|
35
|
+
this._maxFileSize = 50; // ✅ Default 50MB
|
|
36
|
+
this._showReshapeWarning = true;
|
|
37
|
+
this._rawFileData = null; // ✅ Store for re-parsing
|
|
35
38
|
this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
|
|
36
39
|
this._showStatus = options.showStatus ?? true;
|
|
37
40
|
this._icon = options.icon ?? '';
|
|
@@ -45,6 +48,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
45
48
|
};
|
|
46
49
|
this._maxSheetSize = options.maxSheetSize ?? 100000;
|
|
47
50
|
this._sheetChunkSize = options.sheetChunkSize ?? 10000;
|
|
51
|
+
this._maxFileSize = options.maxFileSize ?? 50;
|
|
52
|
+
this._showReshapeWarning = options.showReshapeWarning ?? true;
|
|
48
53
|
}
|
|
49
54
|
getTriggerEvents() { return TRIGGER_EVENTS; }
|
|
50
55
|
getCallbackEvents() { return CALLBACK_EVENTS; }
|
|
@@ -87,6 +92,12 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
87
92
|
if (!files || files.length === 0)
|
|
88
93
|
return;
|
|
89
94
|
const file = files[0];
|
|
95
|
+
// ✅ Check file size
|
|
96
|
+
const fileSizeMB = file.size / (1024 * 1024);
|
|
97
|
+
if (fileSizeMB > this._maxFileSize) {
|
|
98
|
+
this._updateStatus(`❌ File too large (${fileSizeMB.toFixed(1)}MB). Max: ${this._maxFileSize}MB`, 'error');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
90
101
|
this.state.loading = true;
|
|
91
102
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
92
103
|
try {
|
|
@@ -112,8 +123,13 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
112
123
|
}
|
|
113
124
|
}
|
|
114
125
|
else {
|
|
115
|
-
// CSV/TSV:
|
|
116
|
-
const
|
|
126
|
+
// ✅ CSV/TSV: Store raw text for reshaping
|
|
127
|
+
const text = await file.text();
|
|
128
|
+
this._rawFileData = { file, text };
|
|
129
|
+
const df = this._driver.parseCSV(text, {
|
|
130
|
+
autoDetectDelimiter: true,
|
|
131
|
+
hasHeader: true
|
|
132
|
+
});
|
|
117
133
|
await this._driver.store(file.name, df, { source: file.name });
|
|
118
134
|
this._setDataFrame(df, file.name);
|
|
119
135
|
}
|
|
@@ -230,6 +246,13 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
230
246
|
this._sheetChunkSize = v;
|
|
231
247
|
return this;
|
|
232
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* ✅ NEW: Set max file size in MB
|
|
251
|
+
*/
|
|
252
|
+
maxFileSize(mb) {
|
|
253
|
+
this._maxFileSize = mb;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
233
256
|
/* ═══════════════════════════════════════════════════
|
|
234
257
|
* MULTI-SHEET RENDERING
|
|
235
258
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -370,8 +393,17 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
370
393
|
}
|
|
371
394
|
this._updateTable();
|
|
372
395
|
this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
|
|
373
|
-
if
|
|
374
|
-
|
|
396
|
+
// ✅ Add reshape warning button if CSV and enabled
|
|
397
|
+
if (this._showReshapeWarning && this._rawFileData?.text) {
|
|
398
|
+
const statusEl = document.getElementById(`${this._id}-status`);
|
|
399
|
+
if (statusEl) {
|
|
400
|
+
const settingsBtn = document.createElement('button');
|
|
401
|
+
settingsBtn.textContent = 'Settings';
|
|
402
|
+
settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
|
|
403
|
+
settingsBtn.style.marginLeft = '0.5rem';
|
|
404
|
+
settingsBtn.addEventListener('click', () => this._showReshapeModal());
|
|
405
|
+
statusEl.appendChild(settingsBtn);
|
|
406
|
+
}
|
|
375
407
|
}
|
|
376
408
|
this._triggerCallback('load', this._df, null, this);
|
|
377
409
|
}
|
|
@@ -449,6 +481,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
449
481
|
this._table?.rows(filtered.toRows());
|
|
450
482
|
});
|
|
451
483
|
}
|
|
484
|
+
_showReshapeModal() {
|
|
485
|
+
// TODO: Implement reshape modal for re-parsing CSV with different options
|
|
486
|
+
console.warn('Reshape modal not yet implemented');
|
|
487
|
+
}
|
|
452
488
|
update(prop, value) { }
|
|
453
489
|
/* ═══════════════════════════════════════════════════
|
|
454
490
|
* RENDER
|
|
@@ -22,6 +22,8 @@ export interface DataFrameOptions {
|
|
|
22
22
|
icon?: string;
|
|
23
23
|
maxSheetSize?: number; // ✅ NEW: Max rows per sheet (default: 100k)
|
|
24
24
|
sheetChunkSize?: number; // ✅ NEW: Chunk size for large sheets (default: 10k)
|
|
25
|
+
maxFileSize?: number; // ✅ NEW: Max file size in MB (default: 50MB)
|
|
26
|
+
showReshapeWarning?: boolean; // ✅ NEW: Show warning when data looks malformed
|
|
25
27
|
style?: string;
|
|
26
28
|
class?: string;
|
|
27
29
|
}
|
|
@@ -55,6 +57,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
55
57
|
private _icon: string = '';
|
|
56
58
|
private _maxSheetSize: number = 100000; // ✅ Default 100k rows
|
|
57
59
|
private _sheetChunkSize: number = 10000; // ✅ Default 10k chunk
|
|
60
|
+
private _maxFileSize: number = 50; // ✅ Default 50MB
|
|
61
|
+
private _showReshapeWarning: boolean = true;
|
|
62
|
+
private _rawFileData: { file: File; text?: string } | null = null; // ✅ Store for re-parsing
|
|
58
63
|
|
|
59
64
|
constructor(id: string, options: DataFrameOptions = {}) {
|
|
60
65
|
super(id, {
|
|
@@ -87,6 +92,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
87
92
|
};
|
|
88
93
|
this._maxSheetSize = options.maxSheetSize ?? 100000;
|
|
89
94
|
this._sheetChunkSize = options.sheetChunkSize ?? 10000;
|
|
95
|
+
this._maxFileSize = options.maxFileSize ?? 50;
|
|
96
|
+
this._showReshapeWarning = options.showReshapeWarning ?? true;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
protected getTriggerEvents(): readonly string[] { return TRIGGER_EVENTS; }
|
|
@@ -126,6 +133,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
126
133
|
upload.bind('change', async (files: File[]) => {
|
|
127
134
|
if (!files || files.length === 0) return;
|
|
128
135
|
const file = files[0];
|
|
136
|
+
|
|
137
|
+
// ✅ Check file size
|
|
138
|
+
const fileSizeMB = file.size / (1024 * 1024);
|
|
139
|
+
if (fileSizeMB > this._maxFileSize) {
|
|
140
|
+
this._updateStatus(`❌ File too large (${fileSizeMB.toFixed(1)}MB). Max: ${this._maxFileSize}MB`, 'error');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
129
144
|
this.state.loading = true;
|
|
130
145
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
131
146
|
|
|
@@ -154,8 +169,15 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
154
169
|
this._setDataFrame(sheets[sheetNames[0]], file.name);
|
|
155
170
|
}
|
|
156
171
|
} else {
|
|
157
|
-
// CSV/TSV:
|
|
158
|
-
const
|
|
172
|
+
// ✅ CSV/TSV: Store raw text for reshaping
|
|
173
|
+
const text = await file.text();
|
|
174
|
+
this._rawFileData = { file, text };
|
|
175
|
+
|
|
176
|
+
const df = this._driver.parseCSV(text, {
|
|
177
|
+
autoDetectDelimiter: true,
|
|
178
|
+
hasHeader: true
|
|
179
|
+
});
|
|
180
|
+
|
|
159
181
|
await this._driver.store(file.name, df, { source: file.name });
|
|
160
182
|
this._setDataFrame(df, file.name);
|
|
161
183
|
}
|
|
@@ -284,6 +306,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
284
306
|
return this;
|
|
285
307
|
}
|
|
286
308
|
|
|
309
|
+
/**
|
|
310
|
+
* ✅ NEW: Set max file size in MB
|
|
311
|
+
*/
|
|
312
|
+
maxFileSize(mb: number): this {
|
|
313
|
+
this._maxFileSize = mb;
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
|
|
287
317
|
/* ═══════════════════════════════════════════════════
|
|
288
318
|
* MULTI-SHEET RENDERING
|
|
289
319
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -463,8 +493,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
463
493
|
'success'
|
|
464
494
|
);
|
|
465
495
|
|
|
466
|
-
if
|
|
467
|
-
|
|
496
|
+
// ✅ Add reshape warning button if CSV and enabled
|
|
497
|
+
if (this._showReshapeWarning && this._rawFileData?.text) {
|
|
498
|
+
const statusEl = document.getElementById(`${this._id}-status`);
|
|
499
|
+
if (statusEl) {
|
|
500
|
+
const settingsBtn = document.createElement('button');
|
|
501
|
+
settingsBtn.textContent = 'Settings';
|
|
502
|
+
settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
|
|
503
|
+
settingsBtn.style.marginLeft = '0.5rem';
|
|
504
|
+
settingsBtn.addEventListener('click', () => this._showReshapeModal());
|
|
505
|
+
statusEl.appendChild(settingsBtn);
|
|
506
|
+
}
|
|
468
507
|
}
|
|
469
508
|
|
|
470
509
|
this._triggerCallback('load', this._df, null, this);
|
|
@@ -557,6 +596,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
557
596
|
});
|
|
558
597
|
}
|
|
559
598
|
|
|
599
|
+
private _showReshapeModal(): void {
|
|
600
|
+
// TODO: Implement reshape modal for re-parsing CSV with different options
|
|
601
|
+
console.warn('Reshape modal not yet implemented');
|
|
602
|
+
}
|
|
603
|
+
|
|
560
604
|
update(prop: string, value: any): void { }
|
|
561
605
|
|
|
562
606
|
/* ═══════════════════════════════════════════════════
|
|
@@ -20,6 +20,8 @@ export interface ParseOptions {
|
|
|
20
20
|
sheet?: string | number;
|
|
21
21
|
maxSheetSize?: number;
|
|
22
22
|
sheetChunkSize?: number;
|
|
23
|
+
headerRow?: number;
|
|
24
|
+
autoDetectDelimiter?: boolean;
|
|
23
25
|
}
|
|
24
26
|
export declare class TabularDriver {
|
|
25
27
|
private _dbName;
|
|
@@ -27,6 +29,16 @@ export declare class TabularDriver {
|
|
|
27
29
|
private _db;
|
|
28
30
|
constructor(dbName?: string, storeName?: string);
|
|
29
31
|
open(): Promise<IDBDatabase>;
|
|
32
|
+
/**
|
|
33
|
+
* ✅ NEW: Auto-detect delimiter from first N lines of CSV
|
|
34
|
+
* Checks for: , | \t ;
|
|
35
|
+
*/
|
|
36
|
+
private _detectDelimiter;
|
|
37
|
+
/**
|
|
38
|
+
* ✅ NEW: Detect which row contains the header
|
|
39
|
+
* Looks for first row with mostly string values
|
|
40
|
+
*/
|
|
41
|
+
private _detectHeaderRow;
|
|
30
42
|
/**
|
|
31
43
|
* Parse a CSV/TSV string into a DataFrame
|
|
32
44
|
*/
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,17 +28,85 @@ export class TabularDriver {
|
|
|
28
28
|
/* ═══════════════════════════════════════════════════
|
|
29
29
|
* CSV / TSV PARSING
|
|
30
30
|
* ═══════════════════════════════════════════════════ */
|
|
31
|
+
/**
|
|
32
|
+
* ✅ NEW: Auto-detect delimiter from first N lines of CSV
|
|
33
|
+
* Checks for: , | \t ;
|
|
34
|
+
*/
|
|
35
|
+
_detectDelimiter(text, sampleLines = 5) {
|
|
36
|
+
const lines = this._splitLines(text).slice(0, sampleLines).filter(l => l.trim());
|
|
37
|
+
if (lines.length === 0)
|
|
38
|
+
return ',';
|
|
39
|
+
const delimiters = [',', '|', '\t', ';'];
|
|
40
|
+
const scores = {};
|
|
41
|
+
delimiters.forEach(delim => {
|
|
42
|
+
const counts = lines.map(line => {
|
|
43
|
+
// Count occurrences of delimiter NOT inside quotes
|
|
44
|
+
let count = 0;
|
|
45
|
+
let inQuotes = false;
|
|
46
|
+
for (let i = 0; i < line.length; i++) {
|
|
47
|
+
if (line[i] === '"')
|
|
48
|
+
inQuotes = !inQuotes;
|
|
49
|
+
if (!inQuotes && line[i] === delim)
|
|
50
|
+
count++;
|
|
51
|
+
}
|
|
52
|
+
return count;
|
|
53
|
+
});
|
|
54
|
+
// Delimiter should have consistent count across lines
|
|
55
|
+
const avg = counts.reduce((sum, c) => sum + c, 0) / counts.length;
|
|
56
|
+
const variance = counts.reduce((sum, c) => sum + Math.pow(c - avg, 2), 0) / counts.length;
|
|
57
|
+
// Score: high count, low variance
|
|
58
|
+
scores[delim] = avg > 0 ? avg / (1 + variance) : 0;
|
|
59
|
+
});
|
|
60
|
+
// Return delimiter with highest score
|
|
61
|
+
const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0];
|
|
62
|
+
return best[0];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* ✅ NEW: Detect which row contains the header
|
|
66
|
+
* Looks for first row with mostly string values
|
|
67
|
+
*/
|
|
68
|
+
_detectHeaderRow(text, delimiter, maxCheck = 10) {
|
|
69
|
+
const lines = this._splitLines(text).slice(0, maxCheck).filter(l => l.trim());
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const values = this._parseLine(lines[i], delimiter);
|
|
72
|
+
// Skip if mostly empty
|
|
73
|
+
const nonEmpty = values.filter(v => v.trim()).length;
|
|
74
|
+
if (nonEmpty < values.length * 0.5)
|
|
75
|
+
continue;
|
|
76
|
+
// Check if mostly non-numeric (likely headers)
|
|
77
|
+
const nonNumeric = values.filter(v => {
|
|
78
|
+
const trimmed = v.trim();
|
|
79
|
+
return isNaN(Number(trimmed)) && trimmed !== '';
|
|
80
|
+
}).length;
|
|
81
|
+
if (nonNumeric >= values.length * 0.7) {
|
|
82
|
+
return i;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return 0; // Fallback to first row
|
|
86
|
+
}
|
|
31
87
|
/**
|
|
32
88
|
* Parse a CSV/TSV string into a DataFrame
|
|
33
89
|
*/
|
|
34
90
|
parseCSV(text, options = {}) {
|
|
35
|
-
const { delimiter
|
|
91
|
+
const { delimiter: userDelimiter, hasHeader = true, maxRows, skipRows: userSkipRows = 0, columns: selectCols, headerRow: userHeaderRow, autoDetectDelimiter = true } = options;
|
|
92
|
+
// ✅ Auto-detect delimiter if not provided
|
|
93
|
+
const delimiter = userDelimiter || (autoDetectDelimiter ? this._detectDelimiter(text) : ',');
|
|
94
|
+
// ✅ Auto-detect header row if not provided
|
|
95
|
+
const headerRow = userHeaderRow !== undefined ? userHeaderRow : (hasHeader ? this._detectHeaderRow(text, delimiter) : -1);
|
|
36
96
|
const lines = this._splitLines(text);
|
|
37
|
-
let startIdx = skipRows;
|
|
38
97
|
let headers;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
98
|
+
// Skip rows before header
|
|
99
|
+
let startIdx = userSkipRows;
|
|
100
|
+
if (hasHeader && headerRow >= 0) {
|
|
101
|
+
const headerLine = lines[headerRow + userSkipRows];
|
|
102
|
+
if (headerLine) {
|
|
103
|
+
headers = this._parseLine(headerLine, delimiter);
|
|
104
|
+
startIdx = headerRow + userSkipRows + 1;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const firstLine = this._parseLine(lines[startIdx] || '', delimiter);
|
|
108
|
+
headers = firstLine.map((_, i) => `col_${i}`);
|
|
109
|
+
}
|
|
42
110
|
}
|
|
43
111
|
else {
|
|
44
112
|
const firstLine = this._parseLine(lines[startIdx] || '', delimiter);
|
|
@@ -22,6 +22,8 @@ export interface ParseOptions {
|
|
|
22
22
|
sheet?: string | number;
|
|
23
23
|
maxSheetSize?: number; // ✅ NEW: Max rows per sheet to prevent memory issues
|
|
24
24
|
sheetChunkSize?: number; // ✅ NEW: Process Excel in chunks
|
|
25
|
+
headerRow?: number; // ✅ NEW: Which row contains headers (default: 0)
|
|
26
|
+
autoDetectDelimiter?: boolean; // ✅ NEW: Auto-detect delimiter from first lines
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export class TabularDriver {
|
|
@@ -62,25 +64,105 @@ export class TabularDriver {
|
|
|
62
64
|
* CSV / TSV PARSING
|
|
63
65
|
* ═══════════════════════════════════════════════════ */
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* ✅ NEW: Auto-detect delimiter from first N lines of CSV
|
|
69
|
+
* Checks for: , | \t ;
|
|
70
|
+
*/
|
|
71
|
+
private _detectDelimiter(text: string, sampleLines: number = 5): string {
|
|
72
|
+
const lines = this._splitLines(text).slice(0, sampleLines).filter(l => l.trim());
|
|
73
|
+
if (lines.length === 0) return ',';
|
|
74
|
+
|
|
75
|
+
const delimiters = [',', '|', '\t', ';'];
|
|
76
|
+
const scores: Record<string, number> = {};
|
|
77
|
+
|
|
78
|
+
delimiters.forEach(delim => {
|
|
79
|
+
const counts = lines.map(line => {
|
|
80
|
+
// Count occurrences of delimiter NOT inside quotes
|
|
81
|
+
let count = 0;
|
|
82
|
+
let inQuotes = false;
|
|
83
|
+
for (let i = 0; i < line.length; i++) {
|
|
84
|
+
if (line[i] === '"') inQuotes = !inQuotes;
|
|
85
|
+
if (!inQuotes && line[i] === delim) count++;
|
|
86
|
+
}
|
|
87
|
+
return count;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Delimiter should have consistent count across lines
|
|
91
|
+
const avg = counts.reduce((sum, c) => sum + c, 0) / counts.length;
|
|
92
|
+
const variance = counts.reduce((sum, c) => sum + Math.pow(c - avg, 2), 0) / counts.length;
|
|
93
|
+
|
|
94
|
+
// Score: high count, low variance
|
|
95
|
+
scores[delim] = avg > 0 ? avg / (1 + variance) : 0;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Return delimiter with highest score
|
|
99
|
+
const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0];
|
|
100
|
+
return best[0];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* ✅ NEW: Detect which row contains the header
|
|
105
|
+
* Looks for first row with mostly string values
|
|
106
|
+
*/
|
|
107
|
+
private _detectHeaderRow(text: string, delimiter: string, maxCheck: number = 10): number {
|
|
108
|
+
const lines = this._splitLines(text).slice(0, maxCheck).filter(l => l.trim());
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const values = this._parseLine(lines[i], delimiter);
|
|
112
|
+
|
|
113
|
+
// Skip if mostly empty
|
|
114
|
+
const nonEmpty = values.filter(v => v.trim()).length;
|
|
115
|
+
if (nonEmpty < values.length * 0.5) continue;
|
|
116
|
+
|
|
117
|
+
// Check if mostly non-numeric (likely headers)
|
|
118
|
+
const nonNumeric = values.filter(v => {
|
|
119
|
+
const trimmed = v.trim();
|
|
120
|
+
return isNaN(Number(trimmed)) && trimmed !== '';
|
|
121
|
+
}).length;
|
|
122
|
+
|
|
123
|
+
if (nonNumeric >= values.length * 0.7) {
|
|
124
|
+
return i;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return 0; // Fallback to first row
|
|
129
|
+
}
|
|
130
|
+
|
|
65
131
|
/**
|
|
66
132
|
* Parse a CSV/TSV string into a DataFrame
|
|
67
133
|
*/
|
|
68
134
|
parseCSV(text: string, options: ParseOptions = {}): DataFrame {
|
|
69
135
|
const {
|
|
70
|
-
delimiter
|
|
136
|
+
delimiter: userDelimiter,
|
|
71
137
|
hasHeader = true,
|
|
72
138
|
maxRows,
|
|
73
|
-
skipRows = 0,
|
|
74
|
-
columns: selectCols
|
|
139
|
+
skipRows: userSkipRows = 0,
|
|
140
|
+
columns: selectCols,
|
|
141
|
+
headerRow: userHeaderRow,
|
|
142
|
+
autoDetectDelimiter = true
|
|
75
143
|
} = options;
|
|
76
144
|
|
|
145
|
+
// ✅ Auto-detect delimiter if not provided
|
|
146
|
+
const delimiter = userDelimiter || (autoDetectDelimiter ? this._detectDelimiter(text) : ',');
|
|
147
|
+
|
|
148
|
+
// ✅ Auto-detect header row if not provided
|
|
149
|
+
const headerRow = userHeaderRow !== undefined ? userHeaderRow : (hasHeader ? this._detectHeaderRow(text, delimiter) : -1);
|
|
150
|
+
|
|
77
151
|
const lines = this._splitLines(text);
|
|
78
|
-
let startIdx = skipRows;
|
|
79
152
|
let headers: string[];
|
|
80
153
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
154
|
+
// Skip rows before header
|
|
155
|
+
let startIdx = userSkipRows;
|
|
156
|
+
|
|
157
|
+
if (hasHeader && headerRow >= 0) {
|
|
158
|
+
const headerLine = lines[headerRow + userSkipRows];
|
|
159
|
+
if (headerLine) {
|
|
160
|
+
headers = this._parseLine(headerLine, delimiter);
|
|
161
|
+
startIdx = headerRow + userSkipRows + 1;
|
|
162
|
+
} else {
|
|
163
|
+
const firstLine = this._parseLine(lines[startIdx] || '', delimiter);
|
|
164
|
+
headers = firstLine.map((_, i) => `col_${i}`);
|
|
165
|
+
}
|
|
84
166
|
} else {
|
|
85
167
|
const firstLine = this._parseLine(lines[startIdx] || '', delimiter);
|
|
86
168
|
headers = firstLine.map((_, i) => `col_${i}`);
|