chagtptmm commited on
Commit
b79f6b6
·
verified ·
1 Parent(s): 1611d5a

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +469 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Excel Row Comparison Tool
3
- emoji: 🔥
4
- colorFrom: blue
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: excel-row-comparison-tool
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,469 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Excel Row Comparison Tool</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <style>
11
+ .file-input-label {
12
+ transition: all 0.3s ease;
13
+ }
14
+ .file-input-label:hover {
15
+ transform: translateY(-2px);
16
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
17
+ }
18
+ .file-input-label.drag-over {
19
+ border-color: #4f46e5;
20
+ background-color: #eef2ff;
21
+ }
22
+ .result-table {
23
+ max-height: 400px;
24
+ overflow-y: auto;
25
+ }
26
+ .loading-spinner {
27
+ animation: spin 1s linear infinite;
28
+ }
29
+ @keyframes spin {
30
+ 0% { transform: rotate(0deg); }
31
+ 100% { transform: rotate(360deg); }
32
+ }
33
+ </style>
34
+ </head>
35
+ <body class="bg-gray-50 min-h-screen">
36
+ <div class="container mx-auto px-4 py-8">
37
+ <div class="max-w-4xl mx-auto">
38
+ <!-- Header -->
39
+ <header class="text-center mb-10">
40
+ <h1 class="text-3xl md:text-4xl font-bold text-indigo-700 mb-2">Excel Row Comparison Tool</h1>
41
+ <p class="text-gray-600">Upload two Excel files to find matching rows between them</p>
42
+ </header>
43
+
44
+ <!-- Main Content -->
45
+ <main class="bg-white rounded-xl shadow-md overflow-hidden">
46
+ <!-- File Upload Section -->
47
+ <section class="p-6 border-b border-gray-200">
48
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
49
+ <!-- File 1 Upload -->
50
+ <div>
51
+ <label for="file1" class="block text-sm font-medium text-gray-700 mb-2">
52
+ <i class="fas fa-file-excel text-green-600 mr-1"></i>
53
+ First Excel File
54
+ </label>
55
+ <div class="relative">
56
+ <label for="file1" class="file-input-label flex flex-col items-center justify-center w-full h-40 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100">
57
+ <div class="flex flex-col items-center justify-center pt-5 pb-6">
58
+ <i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-3"></i>
59
+ <p class="mb-2 text-sm text-gray-500">
60
+ <span class="font-semibold">Click to upload</span> or drag and drop
61
+ </p>
62
+ <p class="text-xs text-gray-500">XLSX or XLS files only</p>
63
+ </div>
64
+ <input id="file1" type="file" class="hidden" accept=".xlsx,.xls" />
65
+ </label>
66
+ <div id="file1-name" class="mt-2 text-sm text-gray-600 truncate max-w-full"></div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- File 2 Upload -->
71
+ <div>
72
+ <label for="file2" class="block text-sm font-medium text-gray-700 mb-2">
73
+ <i class="fas fa-file-excel text-blue-600 mr-1"></i>
74
+ Second Excel File
75
+ </label>
76
+ <div class="relative">
77
+ <label for="file2" class="file-input-label flex flex-col items-center justify-center w-full h-40 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100">
78
+ <div class="flex flex-col items-center justify-center pt-5 pb-6">
79
+ <i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-3"></i>
80
+ <p class="mb-2 text-sm text-gray-500">
81
+ <span class="font-semibold">Click to upload</span> or drag and drop
82
+ </p>
83
+ <p class="text-xs text-gray-500">XLSX or XLS files only</p>
84
+ </div>
85
+ <input id="file2" type="file" class="hidden" accept=".xlsx,.xls" />
86
+ </label>
87
+ <div id="file2-name" class="mt-2 text-sm text-gray-600 truncate max-w-full"></div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Options Section -->
93
+ <div class="mt-6">
94
+ <h3 class="text-sm font-medium text-gray-700 mb-2">Comparison Options</h3>
95
+ <div class="flex flex-wrap gap-4">
96
+ <label class="inline-flex items-center">
97
+ <input type="checkbox" id="ignore-case" class="rounded text-indigo-600 focus:ring-indigo-500">
98
+ <span class="ml-2 text-sm text-gray-600">Ignore case differences</span>
99
+ </label>
100
+ <label class="inline-flex items-center">
101
+ <input type="checkbox" id="trim-whitespace" checked class="rounded text-indigo-600 focus:ring-indigo-500">
102
+ <span class="ml-2 text-sm text-gray-600">Trim whitespace</span>
103
+ </label>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Compare Button -->
108
+ <div class="mt-8 text-center">
109
+ <button id="compare-btn" class="px-6 py-3 bg-indigo-600 text-white font-medium rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled>
110
+ <i class="fas fa-exchange-alt mr-2"></i> Compare Files
111
+ </button>
112
+ </div>
113
+ </section>
114
+
115
+ <!-- Results Section -->
116
+ <section id="results-section" class="hidden p-6">
117
+ <div class="flex justify-between items-center mb-4">
118
+ <h2 class="text-xl font-semibold text-gray-800">
119
+ <i class="fas fa-clipboard-check text-indigo-600 mr-2"></i>
120
+ Matching Rows Found
121
+ </h2>
122
+ <div class="flex items-center space-x-2">
123
+ <span id="match-count" class="px-3 py-1 bg-indigo-100 text-indigo-800 text-sm font-medium rounded-full">0 matches</span>
124
+ <button id="export-btn" class="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors">
125
+ <i class="fas fa-file-export mr-1"></i> Export
126
+ </button>
127
+ </div>
128
+ </div>
129
+
130
+ <!-- Loading Indicator -->
131
+ <div id="loading-indicator" class="hidden flex justify-center items-center py-10">
132
+ <div class="loading-spinner h-10 w-10 border-4 border-indigo-500 border-t-transparent rounded-full"></div>
133
+ </div>
134
+
135
+ <!-- Results Table -->
136
+ <div id="results-container" class="hidden">
137
+ <div class="result-table border border-gray-200 rounded-lg overflow-hidden">
138
+ <table class="min-w-full divide-y divide-gray-200">
139
+ <thead class="bg-gray-50">
140
+ <tr id="table-headers"></tr>
141
+ </thead>
142
+ <tbody id="table-body" class="bg-white divide-y divide-gray-200"></tbody>
143
+ </table>
144
+ </div>
145
+ <p id="no-results" class="hidden text-center py-6 text-gray-500">
146
+ No matching rows found between the two files.
147
+ </p>
148
+ </div>
149
+
150
+ <!-- Stats -->
151
+ <div id="stats" class="hidden mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 text-sm text-gray-600">
152
+ <div class="bg-gray-50 p-3 rounded-lg">
153
+ <div class="font-medium">File 1 Rows</div>
154
+ <div id="file1-rows" class="text-indigo-600 font-semibold">0</div>
155
+ </div>
156
+ <div class="bg-gray-50 p-3 rounded-lg">
157
+ <div class="font-medium">File 2 Rows</div>
158
+ <div id="file2-rows" class="text-indigo-600 font-semibold">0</div>
159
+ </div>
160
+ <div class="bg-gray-50 p-3 rounded-lg">
161
+ <div class="font-medium">Processing Time</div>
162
+ <div id="processing-time" class="text-indigo-600 font-semibold">0ms</div>
163
+ </div>
164
+ </div>
165
+ </section>
166
+ </main>
167
+
168
+ <!-- Footer -->
169
+ <footer class="mt-10 text-center text-sm text-gray-500">
170
+ <p>This tool compares rows between two Excel files in your browser. Your data never leaves your computer.</p>
171
+ <p class="mt-1">For large files, consider using our <a href="#" class="text-indigo-600 hover:underline">desktop version</a>.</p>
172
+ </footer>
173
+ </div>
174
+ </div>
175
+
176
+ <script>
177
+ document.addEventListener('DOMContentLoaded', function() {
178
+ // DOM Elements
179
+ const file1Input = document.getElementById('file1');
180
+ const file2Input = document.getElementById('file2');
181
+ const file1Name = document.getElementById('file1-name');
182
+ const file2Name = document.getElementById('file2-name');
183
+ const compareBtn = document.getElementById('compare-btn');
184
+ const resultsSection = document.getElementById('results-section');
185
+ const loadingIndicator = document.getElementById('loading-indicator');
186
+ const resultsContainer = document.getElementById('results-container');
187
+ const noResults = document.getElementById('no-results');
188
+ const tableHeaders = document.getElementById('table-headers');
189
+ const tableBody = document.getElementById('table-body');
190
+ const matchCount = document.getElementById('match-count');
191
+ const exportBtn = document.getElementById('export-btn');
192
+ const file1Rows = document.getElementById('file1-rows');
193
+ const file2Rows = document.getElementById('file2-rows');
194
+ const processingTime = document.getElementById('processing-time');
195
+ const stats = document.getElementById('stats');
196
+ const ignoreCase = document.getElementById('ignore-case');
197
+ const trimWhitespace = document.getElementById('trim-whitespace');
198
+
199
+ // File handling
200
+ let file1Data = null;
201
+ let file2Data = null;
202
+ let headers = [];
203
+ let commonRows = [];
204
+
205
+ // Setup drag and drop for file inputs
206
+ setupDragDrop(file1Input, document.querySelector('label[for="file1"]'));
207
+ setupDragDrop(file2Input, document.querySelector('label[for="file2"]'));
208
+
209
+ // File input change handlers
210
+ file1Input.addEventListener('change', function(e) {
211
+ handleFileSelect(e, file1Name, 1);
212
+ });
213
+
214
+ file2Input.addEventListener('change', function(e) {
215
+ handleFileSelect(e, file2Name, 2);
216
+ });
217
+
218
+ // Compare button click handler
219
+ compareBtn.addEventListener('click', compareFiles);
220
+
221
+ // Export button click handler
222
+ exportBtn.addEventListener('click', exportResults);
223
+
224
+ // Functions
225
+ function setupDragDrop(inputElement, labelElement) {
226
+ labelElement.addEventListener('dragover', function(e) {
227
+ e.preventDefault();
228
+ this.classList.add('drag-over');
229
+ });
230
+
231
+ labelElement.addEventListener('dragleave', function(e) {
232
+ e.preventDefault();
233
+ this.classList.remove('drag-over');
234
+ });
235
+
236
+ labelElement.addEventListener('drop', function(e) {
237
+ e.preventDefault();
238
+ this.classList.remove('drag-over');
239
+ if (e.dataTransfer.files.length) {
240
+ inputElement.files = e.dataTransfer.files;
241
+ const event = new Event('change');
242
+ inputElement.dispatchEvent(event);
243
+ }
244
+ });
245
+ }
246
+
247
+ function handleFileSelect(event, nameElement, fileNumber) {
248
+ const file = event.target.files[0];
249
+ if (!file) return;
250
+
251
+ // Update UI with file name
252
+ nameElement.textContent = file.name;
253
+ nameElement.title = file.name;
254
+
255
+ // Read the file
256
+ const reader = new FileReader();
257
+ reader.onload = function(e) {
258
+ try {
259
+ const data = new Uint8Array(e.target.result);
260
+ const workbook = XLSX.read(data, { type: 'array' });
261
+ const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
262
+ const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
263
+
264
+ // Store the data
265
+ if (fileNumber === 1) {
266
+ file1Data = jsonData;
267
+ } else {
268
+ file2Data = jsonData;
269
+ }
270
+
271
+ // Enable compare button if both files are loaded
272
+ if (file1Data && file2Data) {
273
+ compareBtn.disabled = false;
274
+ }
275
+ } catch (error) {
276
+ showError(`Error reading file ${fileNumber}: ${error.message}`);
277
+ if (fileNumber === 1) {
278
+ file1Name.textContent = '';
279
+ file1Data = null;
280
+ } else {
281
+ file2Name.textContent = '';
282
+ file2Data = null;
283
+ }
284
+ compareBtn.disabled = true;
285
+ }
286
+ };
287
+ reader.onerror = function() {
288
+ showError(`Error reading file ${fileNumber}`);
289
+ if (fileNumber === 1) {
290
+ file1Name.textContent = '';
291
+ file1Data = null;
292
+ } else {
293
+ file2Name.textContent = '';
294
+ file2Data = null;
295
+ }
296
+ compareBtn.disabled = true;
297
+ };
298
+ reader.readAsArrayBuffer(file);
299
+ }
300
+
301
+ function compareFiles() {
302
+ // Show loading state
303
+ resultsSection.classList.remove('hidden');
304
+ loadingIndicator.classList.remove('hidden');
305
+ resultsContainer.classList.add('hidden');
306
+ noResults.classList.add('hidden');
307
+ stats.classList.add('hidden');
308
+ compareBtn.disabled = true;
309
+ compareBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Processing...';
310
+
311
+ // Use setTimeout to allow UI to update before heavy processing
312
+ setTimeout(() => {
313
+ const startTime = performance.now();
314
+
315
+ try {
316
+ // Process the data
317
+ headers = file1Data[0] || [];
318
+
319
+ // Get rows (skip headers)
320
+ const rows1 = file1Data.slice(1);
321
+ const rows2 = file2Data.slice(1);
322
+
323
+ // Update stats
324
+ file1Rows.textContent = rows1.length;
325
+ file2Rows.textContent = rows2.length;
326
+
327
+ // Preprocess rows based on options
328
+ const processedRows1 = preprocessRows(rows1);
329
+ const processedRows2 = preprocessRows(rows2);
330
+
331
+ // Find common rows
332
+ commonRows = findCommonRows(processedRows1, processedRows2);
333
+
334
+ // Update UI with results
335
+ updateResultsUI(commonRows);
336
+
337
+ // Update stats
338
+ const endTime = performance.now();
339
+ processingTime.textContent = `${Math.round(endTime - startTime)}ms`;
340
+ stats.classList.remove('hidden');
341
+
342
+ } catch (error) {
343
+ showError(`Error during comparison: ${error.message}`);
344
+ } finally {
345
+ // Reset loading state
346
+ loadingIndicator.classList.add('hidden');
347
+ compareBtn.disabled = false;
348
+ compareBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Compare Files';
349
+ }
350
+ }, 100);
351
+ }
352
+
353
+ function preprocessRows(rows) {
354
+ return rows.map(row => {
355
+ return row.map(cell => {
356
+ let value = cell === undefined || cell === null ? '' : String(cell);
357
+
358
+ if (trimWhitespace.checked) {
359
+ value = value.trim();
360
+ }
361
+
362
+ if (ignoreCase.checked) {
363
+ value = value.toLowerCase();
364
+ }
365
+
366
+ return value;
367
+ });
368
+ });
369
+ }
370
+
371
+ function findCommonRows(rows1, rows2) {
372
+ // Convert rows to strings for easy comparison
373
+ const rowStrings1 = rows1.map(row => JSON.stringify(row));
374
+ const rowStrings2 = rows2.map(row => JSON.stringify(row));
375
+
376
+ // Create sets for faster lookup
377
+ const set1 = new Set(rowStrings1);
378
+ const set2 = new Set(rowStrings2);
379
+
380
+ // Find intersection
381
+ const commonStrings = [...set1].filter(rowStr => set2.has(rowStr));
382
+
383
+ // Convert back to original rows (from rows1)
384
+ return rows1.filter(row => commonStrings.includes(JSON.stringify(row)));
385
+ }
386
+
387
+ function updateResultsUI(commonRows) {
388
+ if (commonRows.length === 0) {
389
+ noResults.classList.remove('hidden');
390
+ resultsContainer.classList.add('hidden');
391
+ matchCount.textContent = '0 matches';
392
+ return;
393
+ }
394
+
395
+ // Update match count
396
+ matchCount.textContent = `${commonRows.length} ${commonRows.length === 1 ? 'match' : 'matches'}`;
397
+
398
+ // Clear previous results
399
+ tableHeaders.innerHTML = '';
400
+ tableBody.innerHTML = '';
401
+
402
+ // Add headers
403
+ headers.forEach(header => {
404
+ const th = document.createElement('th');
405
+ th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
406
+ th.textContent = header || 'Column';
407
+ tableHeaders.appendChild(th);
408
+ });
409
+
410
+ // Add rows
411
+ commonRows.forEach((row, rowIndex) => {
412
+ const tr = document.createElement('tr');
413
+ tr.className = rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50';
414
+
415
+ row.forEach((cell, cellIndex) => {
416
+ const td = document.createElement('td');
417
+ td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
418
+ td.textContent = cell;
419
+
420
+ // Highlight header cells if this is the first row
421
+ if (rowIndex === 0 && cellIndex < headers.length && cell === headers[cellIndex]) {
422
+ td.className += ' font-semibold text-indigo-600';
423
+ }
424
+
425
+ tr.appendChild(td);
426
+ });
427
+
428
+ tableBody.appendChild(tr);
429
+ });
430
+
431
+ resultsContainer.classList.remove('hidden');
432
+ noResults.classList.add('hidden');
433
+ }
434
+
435
+ function exportResults() {
436
+ if (!commonRows.length) {
437
+ showError('No results to export');
438
+ return;
439
+ }
440
+
441
+ try {
442
+ // Create a new workbook
443
+ const wb = XLSX.utils.book_new();
444
+
445
+ // Add headers to the data
446
+ const exportData = [headers, ...commonRows];
447
+
448
+ // Create a worksheet
449
+ const ws = XLSX.utils.aoa_to_sheet(exportData);
450
+
451
+ // Add the worksheet to the workbook
452
+ XLSX.utils.book_append_sheet(wb, ws, "Matching Rows");
453
+
454
+ // Generate the file and trigger download
455
+ XLSX.writeFile(wb, 'matching_rows.xlsx');
456
+
457
+ } catch (error) {
458
+ showError(`Error exporting results: ${error.message}`);
459
+ }
460
+ }
461
+
462
+ function showError(message) {
463
+ // In a real app, you'd want a more sophisticated error display
464
+ alert(`Error: ${message}`);
465
+ }
466
+ });
467
+ </script>
468
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=chagtptmm/excel-row-comparison-tool" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
469
+ </html>