jtcsv 2.1.5 → 2.2.2

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.
@@ -0,0 +1,637 @@
1
+ /**
2
+ * React Integration Example for jtcsv
3
+ *
4
+ * This file demonstrates how to use jtcsv in React applications
5
+ * for CSV import/export functionality.
6
+ *
7
+ * To use: npm install jtcsv react react-dom
8
+ */
9
+
10
+ import React, { useState, useCallback, useMemo } from 'react';
11
+ import { csvToJson, jsonToCsv, ValidationError, ParsingError } from 'jtcsv';
12
+
13
+ // =============================================================================
14
+ // Example 1: CSV Import Component
15
+ // =============================================================================
16
+
17
+ export function CsvImporter({ onImport, columns, parseOptions = {} }) {
18
+ const [error, setError] = useState(null);
19
+ const [preview, setPreview] = useState(null);
20
+ const [isProcessing, setIsProcessing] = useState(false);
21
+
22
+ const handleFileSelect = useCallback(async (event) => {
23
+ const file = event.target.files?.[0];
24
+ if (!file) return;
25
+
26
+ setError(null);
27
+ setIsProcessing(true);
28
+
29
+ try {
30
+ const text = await file.text();
31
+ const data = csvToJson(text, {
32
+ hasHeaders: true,
33
+ trim: true,
34
+ parseNumbers: true,
35
+ parseBooleans: true,
36
+ ...parseOptions
37
+ });
38
+
39
+ // Show preview of first 5 rows
40
+ setPreview({
41
+ total: data.length,
42
+ sample: data.slice(0, 5),
43
+ columns: data.length > 0 ? Object.keys(data[0]) : []
44
+ });
45
+
46
+ // Call onImport with full data
47
+ if (onImport) {
48
+ onImport(data);
49
+ }
50
+ } catch (err) {
51
+ if (err instanceof ParsingError) {
52
+ setError(`Parsing error at line ${err.lineNumber}: ${err.message}`);
53
+ } else if (err instanceof ValidationError) {
54
+ setError(`Validation error: ${err.message}`);
55
+ } else {
56
+ setError(`Error: ${err.message}`);
57
+ }
58
+ setPreview(null);
59
+ } finally {
60
+ setIsProcessing(false);
61
+ }
62
+ }, [onImport, parseOptions]);
63
+
64
+ const clearPreview = useCallback(() => {
65
+ setPreview(null);
66
+ setError(null);
67
+ }, []);
68
+
69
+ return (
70
+ <div className="csv-importer">
71
+ <div className="upload-area">
72
+ <input
73
+ type="file"
74
+ accept=".csv,.tsv,.txt"
75
+ onChange={handleFileSelect}
76
+ disabled={isProcessing}
77
+ />
78
+ {isProcessing && <span>Processing...</span>}
79
+ </div>
80
+
81
+ {error && (
82
+ <div className="error-message" style={{ color: 'red' }}>
83
+ {error}
84
+ </div>
85
+ )}
86
+
87
+ {preview && (
88
+ <div className="preview">
89
+ <h4>Preview ({preview.total} rows total)</h4>
90
+ <p>Columns: {preview.columns.join(', ')}</p>
91
+ <table>
92
+ <thead>
93
+ <tr>
94
+ {preview.columns.map(col => (
95
+ <th key={col}>{col}</th>
96
+ ))}
97
+ </tr>
98
+ </thead>
99
+ <tbody>
100
+ {preview.sample.map((row, i) => (
101
+ <tr key={i}>
102
+ {preview.columns.map(col => (
103
+ <td key={col}>{String(row[col])}</td>
104
+ ))}
105
+ </tr>
106
+ ))}
107
+ </tbody>
108
+ </table>
109
+ <button onClick={clearPreview}>Clear</button>
110
+ </div>
111
+ )}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ // =============================================================================
117
+ // Example 2: CSV Export Component
118
+ // =============================================================================
119
+
120
+ export function CsvExporter({ data, filename = 'export.csv', options = {} }) {
121
+ const [isExporting, setIsExporting] = useState(false);
122
+
123
+ const handleExport = useCallback(() => {
124
+ setIsExporting(true);
125
+
126
+ try {
127
+ const csv = jsonToCsv(data, {
128
+ delimiter: ',',
129
+ includeHeaders: true,
130
+ preventCsvInjection: true,
131
+ ...options
132
+ });
133
+
134
+ // Create blob and download
135
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
136
+ const url = URL.createObjectURL(blob);
137
+ const link = document.createElement('a');
138
+ link.href = url;
139
+ link.download = filename;
140
+ document.body.appendChild(link);
141
+ link.click();
142
+ document.body.removeChild(link);
143
+ URL.revokeObjectURL(url);
144
+ } catch (err) {
145
+ console.error('Export failed:', err);
146
+ alert(`Export failed: ${err.message}`);
147
+ } finally {
148
+ setIsExporting(false);
149
+ }
150
+ }, [data, filename, options]);
151
+
152
+ return (
153
+ <button
154
+ onClick={handleExport}
155
+ disabled={isExporting || !data || data.length === 0}
156
+ >
157
+ {isExporting ? 'Exporting...' : `Export CSV (${data?.length || 0} rows)`}
158
+ </button>
159
+ );
160
+ }
161
+
162
+ // =============================================================================
163
+ // Example 3: Custom Hook for CSV Operations
164
+ // =============================================================================
165
+
166
+ export function useCsv(initialData = []) {
167
+ const [data, setData] = useState(initialData);
168
+ const [error, setError] = useState(null);
169
+ const [isLoading, setIsLoading] = useState(false);
170
+
171
+ const importCsv = useCallback(async (csvString, options = {}) => {
172
+ setIsLoading(true);
173
+ setError(null);
174
+
175
+ try {
176
+ const parsed = csvToJson(csvString, {
177
+ hasHeaders: true,
178
+ trim: true,
179
+ ...options
180
+ });
181
+ setData(parsed);
182
+ return parsed;
183
+ } catch (err) {
184
+ setError(err);
185
+ throw err;
186
+ } finally {
187
+ setIsLoading(false);
188
+ }
189
+ }, []);
190
+
191
+ const exportCsv = useCallback((options = {}) => {
192
+ try {
193
+ return jsonToCsv(data, {
194
+ delimiter: ',',
195
+ includeHeaders: true,
196
+ ...options
197
+ });
198
+ } catch (err) {
199
+ setError(err);
200
+ throw err;
201
+ }
202
+ }, [data]);
203
+
204
+ const importFromFile = useCallback(async (file, options = {}) => {
205
+ const text = await file.text();
206
+ return importCsv(text, options);
207
+ }, [importCsv]);
208
+
209
+ const downloadCsv = useCallback((filename = 'data.csv', options = {}) => {
210
+ const csv = exportCsv(options);
211
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
212
+ const url = URL.createObjectURL(blob);
213
+ const link = document.createElement('a');
214
+ link.href = url;
215
+ link.download = filename;
216
+ link.click();
217
+ URL.revokeObjectURL(url);
218
+ }, [exportCsv]);
219
+
220
+ const clearData = useCallback(() => {
221
+ setData([]);
222
+ setError(null);
223
+ }, []);
224
+
225
+ return {
226
+ data,
227
+ setData,
228
+ error,
229
+ isLoading,
230
+ importCsv,
231
+ exportCsv,
232
+ importFromFile,
233
+ downloadCsv,
234
+ clearData
235
+ };
236
+ }
237
+
238
+ // =============================================================================
239
+ // Example 4: Data Table with CSV Support
240
+ // =============================================================================
241
+
242
+ export function DataTableWithCsv({
243
+ initialData = [],
244
+ columns,
245
+ editable = false,
246
+ onDataChange
247
+ }) {
248
+ const {
249
+ data,
250
+ setData,
251
+ importFromFile,
252
+ downloadCsv,
253
+ isLoading,
254
+ error
255
+ } = useCsv(initialData);
256
+
257
+ const handleFileChange = useCallback(async (event) => {
258
+ const file = event.target.files?.[0];
259
+ if (file) {
260
+ try {
261
+ const imported = await importFromFile(file, {
262
+ parseNumbers: true,
263
+ parseBooleans: true
264
+ });
265
+ if (onDataChange) onDataChange(imported);
266
+ } catch (err) {
267
+ // Error is handled by useCsv hook
268
+ }
269
+ }
270
+ }, [importFromFile, onDataChange]);
271
+
272
+ const handleCellEdit = useCallback((rowIndex, column, value) => {
273
+ const newData = [...data];
274
+ newData[rowIndex] = { ...newData[rowIndex], [column]: value };
275
+ setData(newData);
276
+ if (onDataChange) onDataChange(newData);
277
+ }, [data, setData, onDataChange]);
278
+
279
+ const handleAddRow = useCallback(() => {
280
+ const emptyRow = columns.reduce((acc, col) => ({ ...acc, [col]: '' }), {});
281
+ const newData = [...data, emptyRow];
282
+ setData(newData);
283
+ if (onDataChange) onDataChange(newData);
284
+ }, [data, columns, setData, onDataChange]);
285
+
286
+ const handleDeleteRow = useCallback((rowIndex) => {
287
+ const newData = data.filter((_, i) => i !== rowIndex);
288
+ setData(newData);
289
+ if (onDataChange) onDataChange(newData);
290
+ }, [data, setData, onDataChange]);
291
+
292
+ const displayColumns = columns || (data.length > 0 ? Object.keys(data[0]) : []);
293
+
294
+ return (
295
+ <div className="data-table">
296
+ <div className="toolbar">
297
+ <input
298
+ type="file"
299
+ accept=".csv"
300
+ onChange={handleFileChange}
301
+ disabled={isLoading}
302
+ />
303
+ <button onClick={() => downloadCsv('export.csv')}>
304
+ Export CSV
305
+ </button>
306
+ {editable && (
307
+ <button onClick={handleAddRow}>Add Row</button>
308
+ )}
309
+ </div>
310
+
311
+ {error && (
312
+ <div className="error" style={{ color: 'red' }}>
313
+ {error.message}
314
+ </div>
315
+ )}
316
+
317
+ {isLoading && <div>Loading...</div>}
318
+
319
+ <table>
320
+ <thead>
321
+ <tr>
322
+ {displayColumns.map(col => (
323
+ <th key={col}>{col}</th>
324
+ ))}
325
+ {editable && <th>Actions</th>}
326
+ </tr>
327
+ </thead>
328
+ <tbody>
329
+ {data.map((row, rowIndex) => (
330
+ <tr key={rowIndex}>
331
+ {displayColumns.map(col => (
332
+ <td key={col}>
333
+ {editable ? (
334
+ <input
335
+ value={row[col] ?? ''}
336
+ onChange={(e) => handleCellEdit(rowIndex, col, e.target.value)}
337
+ />
338
+ ) : (
339
+ String(row[col] ?? '')
340
+ )}
341
+ </td>
342
+ ))}
343
+ {editable && (
344
+ <td>
345
+ <button onClick={() => handleDeleteRow(rowIndex)}>Delete</button>
346
+ </td>
347
+ )}
348
+ </tr>
349
+ ))}
350
+ </tbody>
351
+ </table>
352
+ </div>
353
+ );
354
+ }
355
+
356
+ // =============================================================================
357
+ // Example 5: CSV Converter Tool Component
358
+ // =============================================================================
359
+
360
+ export function CsvConverterTool() {
361
+ const [inputText, setInputText] = useState('');
362
+ const [outputText, setOutputText] = useState('');
363
+ const [mode, setMode] = useState('csv-to-json'); // or 'json-to-csv'
364
+ const [error, setError] = useState(null);
365
+ const [options, setOptions] = useState({
366
+ delimiter: ',',
367
+ parseNumbers: true,
368
+ parseBooleans: true,
369
+ prettyPrint: true
370
+ });
371
+
372
+ const convert = useCallback(() => {
373
+ setError(null);
374
+
375
+ try {
376
+ if (mode === 'csv-to-json') {
377
+ const data = csvToJson(inputText, {
378
+ delimiter: options.delimiter,
379
+ parseNumbers: options.parseNumbers,
380
+ parseBooleans: options.parseBooleans
381
+ });
382
+ setOutputText(
383
+ options.prettyPrint
384
+ ? JSON.stringify(data, null, 2)
385
+ : JSON.stringify(data)
386
+ );
387
+ } else {
388
+ const data = JSON.parse(inputText);
389
+ const csv = jsonToCsv(data, {
390
+ delimiter: options.delimiter
391
+ });
392
+ setOutputText(csv);
393
+ }
394
+ } catch (err) {
395
+ setError(err.message);
396
+ }
397
+ }, [inputText, mode, options]);
398
+
399
+ return (
400
+ <div className="csv-converter">
401
+ <div className="mode-selector">
402
+ <label>
403
+ <input
404
+ type="radio"
405
+ value="csv-to-json"
406
+ checked={mode === 'csv-to-json'}
407
+ onChange={() => setMode('csv-to-json')}
408
+ />
409
+ CSV to JSON
410
+ </label>
411
+ <label>
412
+ <input
413
+ type="radio"
414
+ value="json-to-csv"
415
+ checked={mode === 'json-to-csv'}
416
+ onChange={() => setMode('json-to-csv')}
417
+ />
418
+ JSON to CSV
419
+ </label>
420
+ </div>
421
+
422
+ <div className="options">
423
+ <label>
424
+ Delimiter:
425
+ <select
426
+ value={options.delimiter}
427
+ onChange={(e) => setOptions({ ...options, delimiter: e.target.value })}
428
+ >
429
+ <option value=",">Comma (,)</option>
430
+ <option value=";">Semicolon (;)</option>
431
+ <option value="\t">Tab</option>
432
+ <option value="|">Pipe (|)</option>
433
+ </select>
434
+ </label>
435
+
436
+ {mode === 'csv-to-json' && (
437
+ <>
438
+ <label>
439
+ <input
440
+ type="checkbox"
441
+ checked={options.parseNumbers}
442
+ onChange={(e) => setOptions({ ...options, parseNumbers: e.target.checked })}
443
+ />
444
+ Parse Numbers
445
+ </label>
446
+ <label>
447
+ <input
448
+ type="checkbox"
449
+ checked={options.parseBooleans}
450
+ onChange={(e) => setOptions({ ...options, parseBooleans: e.target.checked })}
451
+ />
452
+ Parse Booleans
453
+ </label>
454
+ <label>
455
+ <input
456
+ type="checkbox"
457
+ checked={options.prettyPrint}
458
+ onChange={(e) => setOptions({ ...options, prettyPrint: e.target.checked })}
459
+ />
460
+ Pretty Print
461
+ </label>
462
+ </>
463
+ )}
464
+ </div>
465
+
466
+ <div className="converter-panels">
467
+ <div className="input-panel">
468
+ <h4>Input ({mode === 'csv-to-json' ? 'CSV' : 'JSON'})</h4>
469
+ <textarea
470
+ value={inputText}
471
+ onChange={(e) => setInputText(e.target.value)}
472
+ placeholder={mode === 'csv-to-json'
473
+ ? 'Paste CSV here...\nname,age\nJohn,30'
474
+ : 'Paste JSON array here...\n[{"name":"John","age":30}]'
475
+ }
476
+ rows={10}
477
+ />
478
+ </div>
479
+
480
+ <div className="convert-button">
481
+ <button onClick={convert}>Convert →</button>
482
+ </div>
483
+
484
+ <div className="output-panel">
485
+ <h4>Output ({mode === 'csv-to-json' ? 'JSON' : 'CSV'})</h4>
486
+ <textarea
487
+ value={outputText}
488
+ readOnly
489
+ rows={10}
490
+ />
491
+ </div>
492
+ </div>
493
+
494
+ {error && (
495
+ <div className="error" style={{ color: 'red' }}>
496
+ Error: {error}
497
+ </div>
498
+ )}
499
+ </div>
500
+ );
501
+ }
502
+
503
+ // =============================================================================
504
+ // Example 6: Async Data Loader with CSV Support
505
+ // =============================================================================
506
+
507
+ export function useAsyncCsvLoader(url) {
508
+ const [data, setData] = useState([]);
509
+ const [isLoading, setIsLoading] = useState(false);
510
+ const [error, setError] = useState(null);
511
+
512
+ const load = useCallback(async (parseOptions = {}) => {
513
+ setIsLoading(true);
514
+ setError(null);
515
+
516
+ try {
517
+ const response = await fetch(url);
518
+ if (!response.ok) {
519
+ throw new Error(`HTTP error: ${response.status}`);
520
+ }
521
+ const csvText = await response.text();
522
+ const parsed = csvToJson(csvText, {
523
+ hasHeaders: true,
524
+ trim: true,
525
+ ...parseOptions
526
+ });
527
+ setData(parsed);
528
+ return parsed;
529
+ } catch (err) {
530
+ setError(err);
531
+ throw err;
532
+ } finally {
533
+ setIsLoading(false);
534
+ }
535
+ }, [url]);
536
+
537
+ return { data, isLoading, error, load };
538
+ }
539
+
540
+ // Example usage component
541
+ export function RemoteCsvLoader({ url }) {
542
+ const { data, isLoading, error, load } = useAsyncCsvLoader(url);
543
+
544
+ React.useEffect(() => {
545
+ load({ parseNumbers: true });
546
+ }, [load]);
547
+
548
+ if (isLoading) return <div>Loading CSV from {url}...</div>;
549
+ if (error) return <div>Error: {error.message}</div>;
550
+
551
+ return (
552
+ <div>
553
+ <h4>Loaded {data.length} rows</h4>
554
+ <pre>{JSON.stringify(data.slice(0, 5), null, 2)}</pre>
555
+ </div>
556
+ );
557
+ }
558
+
559
+ // =============================================================================
560
+ // Example 7: Complete App Example
561
+ // =============================================================================
562
+
563
+ export function CsvManagerApp() {
564
+ const [activeTab, setActiveTab] = useState('import');
565
+ const [data, setData] = useState([]);
566
+
567
+ return (
568
+ <div className="csv-manager-app">
569
+ <h1>CSV Manager</h1>
570
+
571
+ <div className="tabs">
572
+ <button
573
+ className={activeTab === 'import' ? 'active' : ''}
574
+ onClick={() => setActiveTab('import')}
575
+ >
576
+ Import
577
+ </button>
578
+ <button
579
+ className={activeTab === 'edit' ? 'active' : ''}
580
+ onClick={() => setActiveTab('edit')}
581
+ >
582
+ Edit
583
+ </button>
584
+ <button
585
+ className={activeTab === 'export' ? 'active' : ''}
586
+ onClick={() => setActiveTab('export')}
587
+ >
588
+ Export
589
+ </button>
590
+ <button
591
+ className={activeTab === 'convert' ? 'active' : ''}
592
+ onClick={() => setActiveTab('convert')}
593
+ >
594
+ Convert
595
+ </button>
596
+ </div>
597
+
598
+ <div className="tab-content">
599
+ {activeTab === 'import' && (
600
+ <CsvImporter
601
+ onImport={(imported) => {
602
+ setData(imported);
603
+ setActiveTab('edit');
604
+ }}
605
+ parseOptions={{ parseNumbers: true, parseBooleans: true }}
606
+ />
607
+ )}
608
+
609
+ {activeTab === 'edit' && (
610
+ <DataTableWithCsv
611
+ initialData={data}
612
+ editable={true}
613
+ onDataChange={setData}
614
+ />
615
+ )}
616
+
617
+ {activeTab === 'export' && (
618
+ <div>
619
+ <h3>Export Options</h3>
620
+ <CsvExporter
621
+ data={data}
622
+ filename="export.csv"
623
+ options={{ delimiter: ',' }}
624
+ />
625
+ </div>
626
+ )}
627
+
628
+ {activeTab === 'convert' && (
629
+ <CsvConverterTool />
630
+ )}
631
+ </div>
632
+ </div>
633
+ );
634
+ }
635
+
636
+ // Default export
637
+ export default CsvManagerApp;