juxscript 1.1.239 → 1.1.243
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/index.js +7 -137
- package/lib/components/dataframe.ts +0 -569
- package/lib/components/tag.ts +68 -0
- package/lib/styles/shadcn.css +20 -7
- package/lib/utils/idgen.ts +6 -0
- package/package.json +5 -6
- package/index.d.ts +0 -239
- package/index.d.ts.map +0 -1
- package/lib/components/alert.d.ts +0 -36
- package/lib/components/alert.d.ts.map +0 -1
- package/lib/components/alert.js +0 -172
- package/lib/components/alert.ts +0 -219
- package/lib/components/app.d.ts +0 -89
- package/lib/components/app.d.ts.map +0 -1
- package/lib/components/app.js +0 -175
- package/lib/components/app.ts +0 -247
- package/lib/components/badge.d.ts +0 -26
- package/lib/components/badge.d.ts.map +0 -1
- package/lib/components/badge.js +0 -91
- package/lib/components/badge.ts +0 -118
- package/lib/components/base/Animations.d.ts +0 -36
- package/lib/components/base/Animations.d.ts.map +0 -1
- package/lib/components/base/Animations.js +0 -70
- package/lib/components/base/Animations.ts +0 -112
- package/lib/components/base/BaseComponent.d.ts +0 -294
- package/lib/components/base/BaseComponent.d.ts.map +0 -1
- package/lib/components/base/BaseComponent.js +0 -735
- package/lib/components/base/BaseComponent.ts +0 -884
- package/lib/components/base/FormInput.d.ts +0 -77
- package/lib/components/base/FormInput.d.ts.map +0 -1
- package/lib/components/base/FormInput.js +0 -171
- package/lib/components/base/FormInput.ts +0 -237
- package/lib/components/blueprint.d.ts +0 -40
- package/lib/components/blueprint.d.ts.map +0 -1
- package/lib/components/blueprint.js +0 -327
- package/lib/components/button.d.ts +0 -70
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js +0 -177
- package/lib/components/button.ts +0 -237
- package/lib/components/card.d.ts +0 -35
- package/lib/components/card.d.ts.map +0 -1
- package/lib/components/card.js +0 -130
- package/lib/components/card.ts +0 -177
- package/lib/components/chart.d.ts +0 -49
- package/lib/components/chart.d.ts.map +0 -1
- package/lib/components/chart.js +0 -205
- package/lib/components/chart.ts +0 -254
- package/lib/components/checkbox.d.ts +0 -33
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js +0 -202
- package/lib/components/checkbox.ts +0 -260
- package/lib/components/code.d.ts +0 -52
- package/lib/components/code.d.ts.map +0 -1
- package/lib/components/code.js +0 -201
- package/lib/components/code.ts +0 -260
- package/lib/components/container.d.ts +0 -60
- package/lib/components/container.d.ts.map +0 -1
- package/lib/components/container.js +0 -195
- package/lib/components/container.ts +0 -259
- package/lib/components/data.d.ts +0 -36
- package/lib/components/data.d.ts.map +0 -1
- package/lib/components/data.js +0 -110
- package/lib/components/data.ts +0 -135
- package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
- package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
- package/lib/components/dataframe/DataFrameSource.js +0 -421
- package/lib/components/dataframe/DataFrameSource.ts +0 -532
- package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
- package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
- package/lib/components/dataframe/ImportSettingsModal.js +0 -442
- package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
- package/lib/components/dataframe.d.ts +0 -110
- package/lib/components/dataframe.d.ts.map +0 -1
- package/lib/components/dataframe.js +0 -470
- package/lib/components/datepicker.d.ts +0 -40
- package/lib/components/datepicker.d.ts.map +0 -1
- package/lib/components/datepicker.js +0 -193
- package/lib/components/datepicker.ts +0 -251
- package/lib/components/dialog.d.ts +0 -39
- package/lib/components/dialog.d.ts.map +0 -1
- package/lib/components/dialog.js +0 -131
- package/lib/components/dialog.ts +0 -178
- package/lib/components/divider.d.ts +0 -31
- package/lib/components/divider.d.ts.map +0 -1
- package/lib/components/divider.js +0 -72
- package/lib/components/divider.ts +0 -104
- package/lib/components/dropdown-menu.d.ts +0 -42
- package/lib/components/dropdown-menu.d.ts.map +0 -1
- package/lib/components/dropdown-menu.js +0 -177
- package/lib/components/dropdown-menu.ts +0 -214
- package/lib/components/dropdown.d.ts +0 -41
- package/lib/components/dropdown.d.ts.map +0 -1
- package/lib/components/dropdown.js +0 -136
- package/lib/components/dropdown.ts +0 -188
- package/lib/components/element.d.ts +0 -51
- package/lib/components/element.d.ts.map +0 -1
- package/lib/components/element.js +0 -209
- package/lib/components/element.ts +0 -271
- package/lib/components/event-chain.d.ts +0 -9
- package/lib/components/event-chain.d.ts.map +0 -1
- package/lib/components/event-chain.js +0 -33
- package/lib/components/fileupload.d.ts +0 -98
- package/lib/components/fileupload.d.ts.map +0 -1
- package/lib/components/fileupload.js +0 -351
- package/lib/components/fileupload.ts +0 -449
- package/lib/components/grid.d.ts +0 -88
- package/lib/components/grid.d.ts.map +0 -1
- package/lib/components/grid.js +0 -208
- package/lib/components/grid.ts +0 -295
- package/lib/components/heading.d.ts +0 -25
- package/lib/components/heading.d.ts.map +0 -1
- package/lib/components/heading.js +0 -83
- package/lib/components/heading.ts +0 -113
- package/lib/components/helpers.d.ts +0 -9
- package/lib/components/helpers.d.ts.map +0 -1
- package/lib/components/helpers.js +0 -30
- package/lib/components/helpers.ts +0 -41
- package/lib/components/hero.d.ts +0 -60
- package/lib/components/hero.d.ts.map +0 -1
- package/lib/components/hero.js +0 -239
- package/lib/components/hero.ts +0 -302
- package/lib/components/history/StateHistory.d.ts +0 -91
- package/lib/components/history/StateHistory.d.ts.map +0 -1
- package/lib/components/history/StateHistory.js +0 -154
- package/lib/components/history/StateHistory.ts +0 -200
- package/lib/components/icon.d.ts +0 -36
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js +0 -135
- package/lib/components/icon.ts +0 -182
- package/lib/components/icons.d.ts +0 -25
- package/lib/components/icons.d.ts.map +0 -1
- package/lib/components/icons.js +0 -440
- package/lib/components/icons.ts +0 -464
- package/lib/components/image.d.ts +0 -42
- package/lib/components/image.d.ts.map +0 -1
- package/lib/components/image.js +0 -204
- package/lib/components/image.ts +0 -260
- package/lib/components/include.d.ts +0 -86
- package/lib/components/include.d.ts.map +0 -1
- package/lib/components/include.js +0 -238
- package/lib/components/include.ts +0 -281
- package/lib/components/input.d.ts +0 -85
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js +0 -362
- package/lib/components/input.ts +0 -473
- package/lib/components/layer.d.ts +0 -72
- package/lib/components/layer.d.ts.map +0 -1
- package/lib/components/layer.js +0 -219
- package/lib/components/layer.ts +0 -304
- package/lib/components/link.d.ts +0 -41
- package/lib/components/link.d.ts.map +0 -1
- package/lib/components/link.js +0 -216
- package/lib/components/link.ts +0 -268
- package/lib/components/list.d.ts +0 -83
- package/lib/components/list.d.ts.map +0 -1
- package/lib/components/list.js +0 -314
- package/lib/components/list.ts +0 -423
- package/lib/components/loading.d.ts +0 -25
- package/lib/components/loading.d.ts.map +0 -1
- package/lib/components/loading.js +0 -76
- package/lib/components/loading.ts +0 -104
- package/lib/components/menu.d.ts +0 -38
- package/lib/components/menu.d.ts.map +0 -1
- package/lib/components/menu.js +0 -205
- package/lib/components/menu.ts +0 -279
- package/lib/components/modal.d.ts +0 -97
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js +0 -463
- package/lib/components/modal.ts +0 -576
- package/lib/components/nav.d.ts +0 -46
- package/lib/components/nav.d.ts.map +0 -1
- package/lib/components/nav.js +0 -193
- package/lib/components/nav.ts +0 -261
- package/lib/components/paragraph.d.ts +0 -30
- package/lib/components/paragraph.d.ts.map +0 -1
- package/lib/components/paragraph.js +0 -93
- package/lib/components/paragraph.ts +0 -123
- package/lib/components/pen.d.ts +0 -125
- package/lib/components/pen.d.ts.map +0 -1
- package/lib/components/pen.js +0 -443
- package/lib/components/pen.ts +0 -567
- package/lib/components/progress.d.ts +0 -40
- package/lib/components/progress.d.ts.map +0 -1
- package/lib/components/progress.js +0 -116
- package/lib/components/progress.ts +0 -163
- package/lib/components/radio.d.ts +0 -43
- package/lib/components/radio.d.ts.map +0 -1
- package/lib/components/radio.js +0 -226
- package/lib/components/radio.ts +0 -303
- package/lib/components/registry.d.ts +0 -34
- package/lib/components/registry.d.ts.map +0 -1
- package/lib/components/registry.js +0 -163
- package/lib/components/registry.ts +0 -193
- package/lib/components/req.d.ts +0 -155
- package/lib/components/req.d.ts.map +0 -1
- package/lib/components/req.js +0 -253
- package/lib/components/req.ts +0 -303
- package/lib/components/script.d.ts +0 -14
- package/lib/components/script.d.ts.map +0 -1
- package/lib/components/script.js +0 -33
- package/lib/components/script.ts +0 -41
- package/lib/components/select.d.ts +0 -42
- package/lib/components/select.d.ts.map +0 -1
- package/lib/components/select.js +0 -209
- package/lib/components/select.ts +0 -281
- package/lib/components/sidebar.d.ts +0 -59
- package/lib/components/sidebar.d.ts.map +0 -1
- package/lib/components/sidebar.js +0 -298
- package/lib/components/sidebar.ts +0 -395
- package/lib/components/stack/BaseStack.d.ts +0 -65
- package/lib/components/stack/BaseStack.d.ts.map +0 -1
- package/lib/components/stack/BaseStack.js +0 -274
- package/lib/components/stack/BaseStack.ts +0 -328
- package/lib/components/stack/HStack.d.ts +0 -18
- package/lib/components/stack/HStack.d.ts.map +0 -1
- package/lib/components/stack/HStack.js +0 -22
- package/lib/components/stack/HStack.ts +0 -25
- package/lib/components/stack/VStack.d.ts +0 -19
- package/lib/components/stack/VStack.d.ts.map +0 -1
- package/lib/components/stack/VStack.js +0 -23
- package/lib/components/stack/VStack.ts +0 -26
- package/lib/components/stack/ZStack.d.ts +0 -18
- package/lib/components/stack/ZStack.d.ts.map +0 -1
- package/lib/components/stack/ZStack.js +0 -22
- package/lib/components/stack/ZStack.ts +0 -25
- package/lib/components/style.d.ts +0 -14
- package/lib/components/style.d.ts.map +0 -1
- package/lib/components/style.js +0 -33
- package/lib/components/style.ts +0 -41
- package/lib/components/switch.d.ts +0 -34
- package/lib/components/switch.d.ts.map +0 -1
- package/lib/components/switch.js +0 -209
- package/lib/components/switch.ts +0 -272
- package/lib/components/table.d.ts +0 -137
- package/lib/components/table.d.ts.map +0 -1
- package/lib/components/table.js +0 -1019
- package/lib/components/table.ts +0 -1225
- package/lib/components/tabs.d.ts +0 -53
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js +0 -275
- package/lib/components/tabs.ts +0 -349
- package/lib/components/theme-toggle.d.ts +0 -45
- package/lib/components/theme-toggle.d.ts.map +0 -1
- package/lib/components/theme-toggle.js +0 -218
- package/lib/components/theme-toggle.ts +0 -297
- package/lib/components/tooltip.d.ts +0 -31
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js +0 -112
- package/lib/components/tooltip.ts +0 -148
- package/lib/components/watcher.d.ts +0 -195
- package/lib/components/watcher.d.ts.map +0 -1
- package/lib/components/watcher.js +0 -241
- package/lib/components/watcher.ts +0 -261
- package/lib/components/write.d.ts +0 -107
- package/lib/components/write.d.ts.map +0 -1
- package/lib/components/write.js +0 -222
- package/lib/components/write.ts +0 -272
- package/lib/data/DataPipeline.d.ts +0 -113
- package/lib/data/DataPipeline.d.ts.map +0 -1
- package/lib/data/DataPipeline.js +0 -359
- package/lib/data/DataPipeline.ts +0 -452
- package/lib/facades/dataframe.jux +0 -0
- package/lib/globals.d.ts +0 -21
- package/lib/reactivity/state.d.ts +0 -36
- package/lib/reactivity/state.d.ts.map +0 -1
- package/lib/reactivity/state.js +0 -67
- package/lib/reactivity/state.ts +0 -78
- package/lib/storage/DataFrame.d.ts +0 -284
- package/lib/storage/DataFrame.d.ts.map +0 -1
- package/lib/storage/DataFrame.js +0 -1022
- package/lib/storage/DataFrame.ts +0 -1195
- package/lib/storage/DataFrameSource.d.ts +0 -158
- package/lib/storage/DataFrameSource.d.ts.map +0 -1
- package/lib/storage/DataFrameSource.js +0 -409
- package/lib/storage/DataFrameSource.ts +0 -556
- package/lib/storage/FileStorage.d.ts +0 -53
- package/lib/storage/FileStorage.d.ts.map +0 -1
- package/lib/storage/FileStorage.js +0 -80
- package/lib/storage/FileStorage.ts +0 -95
- package/lib/storage/IndexedDBDriver.d.ts +0 -75
- package/lib/storage/IndexedDBDriver.d.ts.map +0 -1
- package/lib/storage/IndexedDBDriver.js +0 -177
- package/lib/storage/IndexedDBDriver.ts +0 -226
- package/lib/storage/TabularDriver.d.ts +0 -118
- package/lib/storage/TabularDriver.d.ts.map +0 -1
- package/lib/storage/TabularDriver.js +0 -731
- package/lib/storage/TabularDriver.ts +0 -874
- package/lib/utils/codeparser.d.ts +0 -29
- package/lib/utils/codeparser.d.ts.map +0 -1
- package/lib/utils/codeparser.js +0 -409
- package/lib/utils/fetch.d.ts +0 -176
- package/lib/utils/fetch.d.ts.map +0 -1
- package/lib/utils/fetch.js +0 -427
- package/lib/utils/formatId.d.ts +0 -16
- package/lib/utils/formatId.d.ts.map +0 -1
- package/lib/utils/formatId.js +0 -27
- package/lib/utils/path-resolver.js +0 -23
|
@@ -1,731 +0,0 @@
|
|
|
1
|
-
import { DataFrame } from './DataFrame.js';
|
|
2
|
-
export class TabularDriver {
|
|
3
|
-
constructor(dbName = 'jux-tabular', storeName = 'tables') {
|
|
4
|
-
this._db = null;
|
|
5
|
-
this._dbName = dbName;
|
|
6
|
-
this._storeName = storeName;
|
|
7
|
-
}
|
|
8
|
-
async open() {
|
|
9
|
-
if (this._db)
|
|
10
|
-
return this._db;
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
const request = indexedDB.open(this._dbName, 1);
|
|
13
|
-
request.onupgradeneeded = (e) => {
|
|
14
|
-
const db = e.target.result;
|
|
15
|
-
if (!db.objectStoreNames.contains(this._storeName)) {
|
|
16
|
-
const store = db.createObjectStore(this._storeName, { keyPath: 'id' });
|
|
17
|
-
store.createIndex('name', 'name', { unique: false });
|
|
18
|
-
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
request.onsuccess = (e) => {
|
|
22
|
-
this._db = e.target.result;
|
|
23
|
-
resolve(this._db);
|
|
24
|
-
};
|
|
25
|
-
request.onerror = () => reject(request.error);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
/* ═══════════════════════════════════════════════════
|
|
29
|
-
* CSV / TSV PARSING
|
|
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
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Parse a CSV/TSV string into a DataFrame
|
|
89
|
-
*/
|
|
90
|
-
parseCSV(text, options = {}) {
|
|
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);
|
|
96
|
-
const lines = this._splitLines(text);
|
|
97
|
-
let headers;
|
|
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
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const firstLine = this._parseLine(lines[startIdx] || '', delimiter);
|
|
113
|
-
headers = firstLine.map((_, i) => `col_${i}`);
|
|
114
|
-
}
|
|
115
|
-
const rows = [];
|
|
116
|
-
const end = maxRows ? Math.min(startIdx + maxRows, lines.length) : lines.length;
|
|
117
|
-
for (let i = startIdx; i < end; i++) {
|
|
118
|
-
if (!lines[i].trim())
|
|
119
|
-
continue;
|
|
120
|
-
const values = this._parseLine(lines[i], delimiter);
|
|
121
|
-
const row = {};
|
|
122
|
-
headers.forEach((h, j) => {
|
|
123
|
-
row[h] = this._autoType(values[j]);
|
|
124
|
-
});
|
|
125
|
-
rows.push(row);
|
|
126
|
-
}
|
|
127
|
-
let df = new DataFrame(rows);
|
|
128
|
-
if (selectCols) {
|
|
129
|
-
df = df.select(...selectCols);
|
|
130
|
-
}
|
|
131
|
-
return df;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Stream-parse a File into a DataFrame, with progress callback.
|
|
135
|
-
* Auto-detects XLSX/XLS and routes accordingly.
|
|
136
|
-
*/
|
|
137
|
-
async streamFile(file, options = {}) {
|
|
138
|
-
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
139
|
-
if (ext === 'xlsx' || ext === 'xls') {
|
|
140
|
-
return this._parseXLSX(file, options);
|
|
141
|
-
}
|
|
142
|
-
const { delimiter = file.name.endsWith('.tsv') ? '\t' : ',', hasHeader = true, chunkSize = 64 * 1024, onProgress, maxRows, skipRows = 0, columns: selectCols } = options;
|
|
143
|
-
const totalSize = file.size;
|
|
144
|
-
let bytesRead = 0;
|
|
145
|
-
let headers = null;
|
|
146
|
-
let lineBuffer = '';
|
|
147
|
-
let rowCount = 0;
|
|
148
|
-
let skipped = 0;
|
|
149
|
-
const rows = [];
|
|
150
|
-
const reader = file.stream().getReader();
|
|
151
|
-
const decoder = new TextDecoder();
|
|
152
|
-
while (true) {
|
|
153
|
-
const { done, value } = await reader.read();
|
|
154
|
-
if (done)
|
|
155
|
-
break;
|
|
156
|
-
bytesRead += value.byteLength;
|
|
157
|
-
lineBuffer += decoder.decode(value, { stream: !done });
|
|
158
|
-
const lines = lineBuffer.split('\n');
|
|
159
|
-
lineBuffer = lines.pop() || '';
|
|
160
|
-
for (const line of lines) {
|
|
161
|
-
if (!line.trim())
|
|
162
|
-
continue;
|
|
163
|
-
if (!headers && hasHeader) {
|
|
164
|
-
headers = this._parseLine(line, delimiter);
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (!headers) {
|
|
168
|
-
const vals = this._parseLine(line, delimiter);
|
|
169
|
-
headers = vals.map((_, i) => `col_${i}`);
|
|
170
|
-
}
|
|
171
|
-
if (skipped < skipRows) {
|
|
172
|
-
skipped++;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (maxRows && rowCount >= maxRows)
|
|
176
|
-
break;
|
|
177
|
-
const values = this._parseLine(line, delimiter);
|
|
178
|
-
const row = {};
|
|
179
|
-
headers.forEach((h, j) => {
|
|
180
|
-
row[h] = this._autoType(values[j]);
|
|
181
|
-
});
|
|
182
|
-
rows.push(row);
|
|
183
|
-
rowCount++;
|
|
184
|
-
}
|
|
185
|
-
if (onProgress) {
|
|
186
|
-
onProgress(bytesRead, totalSize);
|
|
187
|
-
}
|
|
188
|
-
if (maxRows && rowCount >= maxRows)
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
// Handle remaining buffer
|
|
192
|
-
if (lineBuffer.trim() && (!maxRows || rowCount < maxRows)) {
|
|
193
|
-
if (!headers) {
|
|
194
|
-
headers = this._parseLine(lineBuffer, delimiter).map((_, i) => `col_${i}`);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
const values = this._parseLine(lineBuffer, delimiter);
|
|
198
|
-
const row = {};
|
|
199
|
-
headers.forEach((h, j) => {
|
|
200
|
-
row[h] = this._autoType(values[j]);
|
|
201
|
-
});
|
|
202
|
-
rows.push(row);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (onProgress)
|
|
206
|
-
onProgress(totalSize, totalSize);
|
|
207
|
-
let df = new DataFrame(rows);
|
|
208
|
-
if (selectCols)
|
|
209
|
-
df = df.select(...selectCols);
|
|
210
|
-
return df;
|
|
211
|
-
}
|
|
212
|
-
/* ═══════════════════════════════════════════════════
|
|
213
|
-
* XLSX / XLS PARSING (ExcelJS)
|
|
214
|
-
* ═══════════════════════════════════════════════════ */
|
|
215
|
-
/**
|
|
216
|
-
* Parse an XLSX/XLS file into a DataFrame using ExcelJS
|
|
217
|
-
*/
|
|
218
|
-
async _parseXLSX(file, options = {}) {
|
|
219
|
-
const { maxRows, skipRows = 0, columns: selectCols, sheet, onProgress, hasHeader = true } = options;
|
|
220
|
-
let ExcelJS;
|
|
221
|
-
try {
|
|
222
|
-
ExcelJS = await import('exceljs');
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
226
|
-
}
|
|
227
|
-
if (onProgress)
|
|
228
|
-
onProgress(0, file.size);
|
|
229
|
-
const buffer = await file.arrayBuffer();
|
|
230
|
-
if (onProgress)
|
|
231
|
-
onProgress(file.size * 0.5, file.size);
|
|
232
|
-
const workbook = new ExcelJS.Workbook();
|
|
233
|
-
await workbook.xlsx.load(buffer);
|
|
234
|
-
// Select sheet
|
|
235
|
-
let worksheet;
|
|
236
|
-
if (typeof sheet === 'number') {
|
|
237
|
-
worksheet = workbook.worksheets[sheet] || workbook.worksheets[0];
|
|
238
|
-
}
|
|
239
|
-
else if (typeof sheet === 'string') {
|
|
240
|
-
worksheet = workbook.getWorksheet(sheet) || workbook.worksheets[0];
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
worksheet = workbook.worksheets[0];
|
|
244
|
-
}
|
|
245
|
-
if (!worksheet) {
|
|
246
|
-
const names = workbook.worksheets.map((ws) => ws.name).join(', ');
|
|
247
|
-
throw new Error(`No worksheet found. Available: ${names}`);
|
|
248
|
-
}
|
|
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);
|
|
272
|
-
});
|
|
273
|
-
if (onProgress)
|
|
274
|
-
onProgress(file.size * 0.9, file.size);
|
|
275
|
-
let rows = allRows;
|
|
276
|
-
if (skipRows > 0)
|
|
277
|
-
rows = rows.slice(skipRows);
|
|
278
|
-
if (maxRows !== undefined)
|
|
279
|
-
rows = rows.slice(0, maxRows);
|
|
280
|
-
if (onProgress)
|
|
281
|
-
onProgress(file.size, file.size);
|
|
282
|
-
let df = new DataFrame(rows);
|
|
283
|
-
if (selectCols)
|
|
284
|
-
df = df.select(...selectCols);
|
|
285
|
-
return df;
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Get sheet names from an XLSX file
|
|
289
|
-
*/
|
|
290
|
-
async getSheetNames(file) {
|
|
291
|
-
let ExcelJS;
|
|
292
|
-
try {
|
|
293
|
-
ExcelJS = await import('exceljs');
|
|
294
|
-
}
|
|
295
|
-
catch {
|
|
296
|
-
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
297
|
-
}
|
|
298
|
-
const buffer = await file.arrayBuffer();
|
|
299
|
-
const workbook = new ExcelJS.Workbook();
|
|
300
|
-
await workbook.xlsx.load(buffer);
|
|
301
|
-
return workbook.worksheets.map((ws) => ws.name);
|
|
302
|
-
}
|
|
303
|
-
/* ═══════════════════════════════════════════════════
|
|
304
|
-
* INDEXEDDB PERSISTENCE
|
|
305
|
-
* ═══════════════════════════════════════════════════ */
|
|
306
|
-
/**
|
|
307
|
-
* Store a DataFrame to IndexedDB
|
|
308
|
-
*/
|
|
309
|
-
async store(name, df, metadata) {
|
|
310
|
-
const db = await this.open();
|
|
311
|
-
const table = {
|
|
312
|
-
id: `${name}-${Date.now()}`,
|
|
313
|
-
name,
|
|
314
|
-
columns: df.columns,
|
|
315
|
-
rows: df.toRows().map(row => df.columns.map(c => row[c])),
|
|
316
|
-
rowCount: df.height,
|
|
317
|
-
delimiter: ',',
|
|
318
|
-
timestamp: Date.now(),
|
|
319
|
-
metadata
|
|
320
|
-
};
|
|
321
|
-
return new Promise((resolve, reject) => {
|
|
322
|
-
const tx = db.transaction(this._storeName, 'readwrite');
|
|
323
|
-
const store = tx.objectStore(this._storeName);
|
|
324
|
-
const request = store.put(table);
|
|
325
|
-
request.onsuccess = () => resolve(table.id);
|
|
326
|
-
request.onerror = () => reject(request.error);
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Load a DataFrame from IndexedDB by ID
|
|
331
|
-
*/
|
|
332
|
-
async load(id) {
|
|
333
|
-
const db = await this.open();
|
|
334
|
-
return new Promise((resolve, reject) => {
|
|
335
|
-
const tx = db.transaction(this._storeName, 'readonly');
|
|
336
|
-
const store = tx.objectStore(this._storeName);
|
|
337
|
-
const request = store.get(id);
|
|
338
|
-
request.onsuccess = () => {
|
|
339
|
-
const table = request.result;
|
|
340
|
-
if (!table)
|
|
341
|
-
return resolve(null);
|
|
342
|
-
const rows = table.rows.map(row => {
|
|
343
|
-
const obj = {};
|
|
344
|
-
table.columns.forEach((col, i) => { obj[col] = row[i]; });
|
|
345
|
-
return obj;
|
|
346
|
-
});
|
|
347
|
-
resolve(new DataFrame(rows));
|
|
348
|
-
};
|
|
349
|
-
request.onerror = () => reject(request.error);
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Load the most recent table by name
|
|
354
|
-
*/
|
|
355
|
-
async loadByName(name) {
|
|
356
|
-
const db = await this.open();
|
|
357
|
-
return new Promise((resolve, reject) => {
|
|
358
|
-
const tx = db.transaction(this._storeName, 'readonly');
|
|
359
|
-
const store = tx.objectStore(this._storeName);
|
|
360
|
-
const index = store.index('name');
|
|
361
|
-
const request = index.getAll(name);
|
|
362
|
-
request.onsuccess = () => {
|
|
363
|
-
const tables = request.result || [];
|
|
364
|
-
if (tables.length === 0)
|
|
365
|
-
return resolve(null);
|
|
366
|
-
tables.sort((a, b) => b.timestamp - a.timestamp);
|
|
367
|
-
const table = tables[0];
|
|
368
|
-
const rows = table.rows.map(row => {
|
|
369
|
-
const obj = {};
|
|
370
|
-
table.columns.forEach((col, i) => { obj[col] = row[i]; });
|
|
371
|
-
return obj;
|
|
372
|
-
});
|
|
373
|
-
resolve(new DataFrame(rows));
|
|
374
|
-
};
|
|
375
|
-
request.onerror = () => reject(request.error);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* List all stored tables (metadata only)
|
|
380
|
-
*/
|
|
381
|
-
async list() {
|
|
382
|
-
const db = await this.open();
|
|
383
|
-
return new Promise((resolve, reject) => {
|
|
384
|
-
const tx = db.transaction(this._storeName, 'readonly');
|
|
385
|
-
const store = tx.objectStore(this._storeName);
|
|
386
|
-
const request = store.getAll();
|
|
387
|
-
request.onsuccess = () => {
|
|
388
|
-
resolve((request.result || []).map((t) => ({
|
|
389
|
-
id: t.id,
|
|
390
|
-
name: t.name,
|
|
391
|
-
columns: t.columns,
|
|
392
|
-
rowCount: t.rowCount,
|
|
393
|
-
timestamp: t.timestamp
|
|
394
|
-
})));
|
|
395
|
-
};
|
|
396
|
-
request.onerror = () => reject(request.error);
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Delete a stored table
|
|
401
|
-
*/
|
|
402
|
-
async delete(id) {
|
|
403
|
-
const db = await this.open();
|
|
404
|
-
return new Promise((resolve, reject) => {
|
|
405
|
-
const tx = db.transaction(this._storeName, 'readwrite');
|
|
406
|
-
const store = tx.objectStore(this._storeName);
|
|
407
|
-
const request = store.delete(id);
|
|
408
|
-
request.onsuccess = () => resolve();
|
|
409
|
-
request.onerror = () => reject(request.error);
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
async clear() {
|
|
413
|
-
const db = await this.open();
|
|
414
|
-
return new Promise((resolve, reject) => {
|
|
415
|
-
const tx = db.transaction(this._storeName, 'readwrite');
|
|
416
|
-
const store = tx.objectStore(this._storeName);
|
|
417
|
-
const request = store.clear();
|
|
418
|
-
request.onsuccess = () => resolve();
|
|
419
|
-
request.onerror = () => reject(request.error);
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
/* ═══════════════════════════════════════════════════
|
|
423
|
-
* STREAM FROM URL
|
|
424
|
-
* ═══════════════════════════════════════════════════ */
|
|
425
|
-
/**
|
|
426
|
-
* Fetch and stream-parse a remote CSV/TSV file
|
|
427
|
-
*/
|
|
428
|
-
async fetch(url, options = {}) {
|
|
429
|
-
const response = await globalThis.fetch(url);
|
|
430
|
-
if (!response.ok)
|
|
431
|
-
throw new Error(`Failed to fetch: ${response.status}`);
|
|
432
|
-
if (!response.body)
|
|
433
|
-
throw new Error('No response body');
|
|
434
|
-
const contentLength = Number(response.headers.get('content-length')) || null;
|
|
435
|
-
const ext = url.split('.').pop()?.toLowerCase();
|
|
436
|
-
const delimiter = options.delimiter ?? (ext === 'tsv' ? '\t' : ',');
|
|
437
|
-
const { hasHeader = true, chunkSize, onProgress, maxRows, skipRows = 0, columns: selectCols } = options;
|
|
438
|
-
const reader = response.body.getReader();
|
|
439
|
-
const decoder = new TextDecoder();
|
|
440
|
-
let headers = null;
|
|
441
|
-
let lineBuffer = '';
|
|
442
|
-
let rowCount = 0;
|
|
443
|
-
let skipped = 0;
|
|
444
|
-
let bytesRead = 0;
|
|
445
|
-
const rows = [];
|
|
446
|
-
while (true) {
|
|
447
|
-
const { done, value } = await reader.read();
|
|
448
|
-
if (done)
|
|
449
|
-
break;
|
|
450
|
-
bytesRead += value.byteLength;
|
|
451
|
-
lineBuffer += decoder.decode(value, { stream: true });
|
|
452
|
-
const lines = lineBuffer.split('\n');
|
|
453
|
-
lineBuffer = lines.pop() || '';
|
|
454
|
-
for (const line of lines) {
|
|
455
|
-
if (!line.trim())
|
|
456
|
-
continue;
|
|
457
|
-
if (!headers && hasHeader) {
|
|
458
|
-
headers = this._parseLine(line, delimiter);
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
if (!headers) {
|
|
462
|
-
const vals = this._parseLine(line, delimiter);
|
|
463
|
-
headers = vals.map((_, i) => `col_${i}`);
|
|
464
|
-
}
|
|
465
|
-
if (skipped < skipRows) {
|
|
466
|
-
skipped++;
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (maxRows && rowCount >= maxRows)
|
|
470
|
-
break;
|
|
471
|
-
const values = this._parseLine(line, delimiter);
|
|
472
|
-
const row = {};
|
|
473
|
-
headers.forEach((h, j) => { row[h] = this._autoType(values[j]); });
|
|
474
|
-
rows.push(row);
|
|
475
|
-
rowCount++;
|
|
476
|
-
}
|
|
477
|
-
if (onProgress)
|
|
478
|
-
onProgress(bytesRead, contentLength);
|
|
479
|
-
if (maxRows && rowCount >= maxRows)
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
if (lineBuffer.trim() && (!maxRows || rowCount < maxRows)) {
|
|
483
|
-
if (headers) {
|
|
484
|
-
const values = this._parseLine(lineBuffer, delimiter);
|
|
485
|
-
const row = {};
|
|
486
|
-
headers.forEach((h, j) => { row[h] = this._autoType(values[j]); });
|
|
487
|
-
rows.push(row);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (onProgress)
|
|
491
|
-
onProgress(bytesRead, bytesRead);
|
|
492
|
-
let df = new DataFrame(rows);
|
|
493
|
-
if (selectCols)
|
|
494
|
-
df = df.select(...selectCols);
|
|
495
|
-
return df;
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
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).
|
|
538
|
-
*/
|
|
539
|
-
async streamFileMultiSheet(file, options = {}) {
|
|
540
|
-
const { maxSheetSize = 100000, onProgress, headerRow = 0 } = options;
|
|
541
|
-
let ExcelJS;
|
|
542
|
-
try {
|
|
543
|
-
ExcelJS = await import('exceljs');
|
|
544
|
-
}
|
|
545
|
-
catch {
|
|
546
|
-
throw new Error('XLSX support requires the "exceljs" package. Install it with: npm install exceljs');
|
|
547
|
-
}
|
|
548
|
-
if (onProgress)
|
|
549
|
-
onProgress(0, file.size);
|
|
550
|
-
const buffer = await file.arrayBuffer();
|
|
551
|
-
if (onProgress)
|
|
552
|
-
onProgress(file.size * 0.3, file.size);
|
|
553
|
-
const workbook = new ExcelJS.Workbook();
|
|
554
|
-
await workbook.xlsx.load(buffer);
|
|
555
|
-
const sheets = {};
|
|
556
|
-
const totalSheets = workbook.worksheets.length;
|
|
557
|
-
let processedSheets = 0;
|
|
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) {
|
|
563
|
-
processedSheets++;
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
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}`);
|
|
572
|
-
processedSheets++;
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
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);
|
|
579
|
-
const headers = headerValues.map((h, i) => {
|
|
580
|
-
if (h === null || h === undefined || String(h).trim() === '') {
|
|
581
|
-
return `__EMPTY${i > 0 ? '_' + i : ''}`;
|
|
582
|
-
}
|
|
583
|
-
return String(h).trim();
|
|
584
|
-
});
|
|
585
|
-
const validHeaders = headers.filter(h => !h.startsWith('__EMPTY'));
|
|
586
|
-
console.log(`[TabularDriver] Headers (${validHeaders.length} valid / ${headers.length} total):`, headers);
|
|
587
|
-
if (validHeaders.length === 0) {
|
|
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));
|
|
593
|
-
}
|
|
594
|
-
processedSheets++;
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
// Build data rows: everything after the header row
|
|
598
|
-
const rows = [];
|
|
599
|
-
const maxDataRow = Math.min(rowCount, headerExcelRow + maxSheetSize);
|
|
600
|
-
for (let r = headerExcelRow + 1; r <= maxDataRow; r++) {
|
|
601
|
-
const row = worksheet.getRow(r);
|
|
602
|
-
const rowData = this._excelRowToArray(row, colCount);
|
|
603
|
-
// Skip completely empty rows
|
|
604
|
-
const hasContent = rowData.some((cell) => cell !== null && cell !== undefined && String(cell).trim() !== '');
|
|
605
|
-
if (!hasContent)
|
|
606
|
-
continue;
|
|
607
|
-
const rowObj = {};
|
|
608
|
-
for (let j = 0; j < headers.length; j++) {
|
|
609
|
-
rowObj[headers[j]] = rowData[j];
|
|
610
|
-
}
|
|
611
|
-
rows.push(rowObj);
|
|
612
|
-
}
|
|
613
|
-
console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
|
|
614
|
-
if (rows.length > 0) {
|
|
615
|
-
console.log(`[TabularDriver] First row:`, rows[0]);
|
|
616
|
-
}
|
|
617
|
-
if (rows.length > 0) {
|
|
618
|
-
sheets[sheetName] = new DataFrame(rows);
|
|
619
|
-
}
|
|
620
|
-
processedSheets++;
|
|
621
|
-
if (onProgress) {
|
|
622
|
-
const progress = 0.3 + (0.6 * (processedSheets / totalSheets));
|
|
623
|
-
onProgress(file.size * progress, file.size);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
if (onProgress)
|
|
627
|
-
onProgress(file.size, file.size);
|
|
628
|
-
return sheets;
|
|
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
|
-
}
|
|
665
|
-
/* ═══════════════════════════════════════════════════
|
|
666
|
-
* INTERNAL PARSING HELPERS
|
|
667
|
-
* ═══════════════════════════════════════════════════ */
|
|
668
|
-
_splitLines(text) {
|
|
669
|
-
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
670
|
-
}
|
|
671
|
-
_parseLine(line, delimiter) {
|
|
672
|
-
const result = [];
|
|
673
|
-
let current = '';
|
|
674
|
-
let inQuotes = false;
|
|
675
|
-
for (let i = 0; i < line.length; i++) {
|
|
676
|
-
const ch = line[i];
|
|
677
|
-
if (inQuotes) {
|
|
678
|
-
if (ch === '"') {
|
|
679
|
-
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
680
|
-
current += '"';
|
|
681
|
-
i++;
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
inQuotes = false;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
current += ch;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
else {
|
|
692
|
-
if (ch === '"') {
|
|
693
|
-
inQuotes = true;
|
|
694
|
-
}
|
|
695
|
-
else if (ch === delimiter) {
|
|
696
|
-
result.push(current.trim());
|
|
697
|
-
current = '';
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
current += ch;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
result.push(current.trim());
|
|
705
|
-
return result;
|
|
706
|
-
}
|
|
707
|
-
_autoType(value) {
|
|
708
|
-
if (value === undefined || value === '' || value === null)
|
|
709
|
-
return null;
|
|
710
|
-
if (value === 'true')
|
|
711
|
-
return true;
|
|
712
|
-
if (value === 'false')
|
|
713
|
-
return false;
|
|
714
|
-
const num = Number(value);
|
|
715
|
-
if (!isNaN(num) && value.trim() !== '')
|
|
716
|
-
return num;
|
|
717
|
-
return value;
|
|
718
|
-
}
|
|
719
|
-
close() {
|
|
720
|
-
if (this._db) {
|
|
721
|
-
this._db.close();
|
|
722
|
-
this._db = null;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* Factory
|
|
728
|
-
*/
|
|
729
|
-
export function tabularDriver(dbName, storeName) {
|
|
730
|
-
return new TabularDriver(dbName, storeName);
|
|
731
|
-
}
|