-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcsvExporter.js
More file actions
117 lines (106 loc) · 3.44 KB
/
csvExporter.js
File metadata and controls
117 lines (106 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
* utils/csvExporter.js – Smart Web Scraper
* Converts product JSON arrays to CSV and triggers file download.
* Supports large datasets via chunked string building.
*/
/**
* Column definitions: { key, label }
* Adjust order / labels here without touching the rest of the code.
*/
const COLUMNS = [
{ key: 'id', label: 'ID' },
{ key: 'title', label: 'Title' },
{ key: 'price', label: 'Price' },
{ key: 'discount', label: 'Discount' },
{ key: 'rating', label: 'Rating' },
{ key: 'reviews', label: 'Reviews' },
{ key: 'site', label: 'Site' },
{ key: 'link', label: 'URL' },
{ key: 'image', label: 'Image URL' },
{ key: 'scrapedAt', label: 'Scraped At' }
];
/**
* Escape a single CSV cell value.
* - Wraps in quotes if value contains comma, quote, or newline
* - Doubles any internal quotes (RFC 4180)
* @param {*} value
* @returns {string}
*/
function escapeCell(value) {
const str = String(value ?? '');
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
}
/**
* Convert an array of product objects to a CSV string.
* @param {Object[]} products
* @param {Array} columns – optional column override
* @returns {string} CSV content
*/
export function convertToCSV(products, columns = COLUMNS) {
if (!products?.length) return '';
const rows = [];
// Header row
rows.push(columns.map(c => escapeCell(c.label)).join(','));
// Data rows
for (const product of products) {
const row = columns.map(col => escapeCell(product[col.key]));
rows.push(row.join(','));
}
return rows.join('\r\n');
}
/**
* Trigger a CSV file download in the browser.
* Uses the chrome.downloads API when available (extension context),
* falls back to a Blob + anchor click (regular web context).
*
* @param {string} csvContent – full CSV string
* @param {string} filename – desired file name (e.g. "products_123.csv")
*/
export function downloadCSV(csvContent, filename = 'products.csv') {
const BOM = '\uFEFF'; // UTF-8 BOM for Excel compatibility
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
// Prefer chrome.downloads in extension context
if (typeof chrome !== 'undefined' && chrome.downloads?.download) {
chrome.downloads.download(
{ url, filename, saveAs: false },
(downloadId) => {
if (chrome.runtime.lastError) {
console.warn('[CSVExporter] chrome.downloads failed, falling back to anchor');
anchorDownload(url, filename);
}
// Revoke after short delay
setTimeout(() => URL.revokeObjectURL(url), 10_000);
}
);
} else {
anchorDownload(url, filename);
setTimeout(() => URL.revokeObjectURL(url), 10_000);
}
}
/**
* Fallback: create a hidden <a> and click it.
*/
function anchorDownload(url, filename) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
/**
* Generate a timestamped filename.
* @param {string} prefix
* @param {string} ext
* @returns {string}
*/
export function generateFilename(prefix = 'products', ext = 'csv') {
const now = new Date();
const ts = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
return `${prefix}_${ts}.${ext}`;
}