chagtptmm's picture
Add 2 files
b79f6b6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excel Row Comparison Tool</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.file-input-label {
transition: all 0.3s ease;
}
.file-input-label:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.file-input-label.drag-over {
border-color: #4f46e5;
background-color: #eef2ff;
}
.result-table {
max-height: 400px;
overflow-y: auto;
}
.loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<!-- Header -->
<header class="text-center mb-10">
<h1 class="text-3xl md:text-4xl font-bold text-indigo-700 mb-2">Excel Row Comparison Tool</h1>
<p class="text-gray-600">Upload two Excel files to find matching rows between them</p>
</header>
<!-- Main Content -->
<main class="bg-white rounded-xl shadow-md overflow-hidden">
<!-- File Upload Section -->
<section class="p-6 border-b border-gray-200">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- File 1 Upload -->
<div>
<label for="file1" class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-file-excel text-green-600 mr-1"></i>
First Excel File
</label>
<div class="relative">
<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">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-3"></i>
<p class="mb-2 text-sm text-gray-500">
<span class="font-semibold">Click to upload</span> or drag and drop
</p>
<p class="text-xs text-gray-500">XLSX or XLS files only</p>
</div>
<input id="file1" type="file" class="hidden" accept=".xlsx,.xls" />
</label>
<div id="file1-name" class="mt-2 text-sm text-gray-600 truncate max-w-full"></div>
</div>
</div>
<!-- File 2 Upload -->
<div>
<label for="file2" class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-file-excel text-blue-600 mr-1"></i>
Second Excel File
</label>
<div class="relative">
<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">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-3"></i>
<p class="mb-2 text-sm text-gray-500">
<span class="font-semibold">Click to upload</span> or drag and drop
</p>
<p class="text-xs text-gray-500">XLSX or XLS files only</p>
</div>
<input id="file2" type="file" class="hidden" accept=".xlsx,.xls" />
</label>
<div id="file2-name" class="mt-2 text-sm text-gray-600 truncate max-w-full"></div>
</div>
</div>
</div>
<!-- Options Section -->
<div class="mt-6">
<h3 class="text-sm font-medium text-gray-700 mb-2">Comparison Options</h3>
<div class="flex flex-wrap gap-4">
<label class="inline-flex items-center">
<input type="checkbox" id="ignore-case" class="rounded text-indigo-600 focus:ring-indigo-500">
<span class="ml-2 text-sm text-gray-600">Ignore case differences</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" id="trim-whitespace" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span class="ml-2 text-sm text-gray-600">Trim whitespace</span>
</label>
</div>
</div>
<!-- Compare Button -->
<div class="mt-8 text-center">
<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>
<i class="fas fa-exchange-alt mr-2"></i> Compare Files
</button>
</div>
</section>
<!-- Results Section -->
<section id="results-section" class="hidden p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800">
<i class="fas fa-clipboard-check text-indigo-600 mr-2"></i>
Matching Rows Found
</h2>
<div class="flex items-center space-x-2">
<span id="match-count" class="px-3 py-1 bg-indigo-100 text-indigo-800 text-sm font-medium rounded-full">0 matches</span>
<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">
<i class="fas fa-file-export mr-1"></i> Export
</button>
</div>
</div>
<!-- Loading Indicator -->
<div id="loading-indicator" class="hidden flex justify-center items-center py-10">
<div class="loading-spinner h-10 w-10 border-4 border-indigo-500 border-t-transparent rounded-full"></div>
</div>
<!-- Results Table -->
<div id="results-container" class="hidden">
<div class="result-table border border-gray-200 rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr id="table-headers"></tr>
</thead>
<tbody id="table-body" class="bg-white divide-y divide-gray-200"></tbody>
</table>
</div>
<p id="no-results" class="hidden text-center py-6 text-gray-500">
No matching rows found between the two files.
</p>
</div>
<!-- Stats -->
<div id="stats" class="hidden mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 text-sm text-gray-600">
<div class="bg-gray-50 p-3 rounded-lg">
<div class="font-medium">File 1 Rows</div>
<div id="file1-rows" class="text-indigo-600 font-semibold">0</div>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<div class="font-medium">File 2 Rows</div>
<div id="file2-rows" class="text-indigo-600 font-semibold">0</div>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<div class="font-medium">Processing Time</div>
<div id="processing-time" class="text-indigo-600 font-semibold">0ms</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="mt-10 text-center text-sm text-gray-500">
<p>This tool compares rows between two Excel files in your browser. Your data never leaves your computer.</p>
<p class="mt-1">For large files, consider using our <a href="#" class="text-indigo-600 hover:underline">desktop version</a>.</p>
</footer>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const file1Input = document.getElementById('file1');
const file2Input = document.getElementById('file2');
const file1Name = document.getElementById('file1-name');
const file2Name = document.getElementById('file2-name');
const compareBtn = document.getElementById('compare-btn');
const resultsSection = document.getElementById('results-section');
const loadingIndicator = document.getElementById('loading-indicator');
const resultsContainer = document.getElementById('results-container');
const noResults = document.getElementById('no-results');
const tableHeaders = document.getElementById('table-headers');
const tableBody = document.getElementById('table-body');
const matchCount = document.getElementById('match-count');
const exportBtn = document.getElementById('export-btn');
const file1Rows = document.getElementById('file1-rows');
const file2Rows = document.getElementById('file2-rows');
const processingTime = document.getElementById('processing-time');
const stats = document.getElementById('stats');
const ignoreCase = document.getElementById('ignore-case');
const trimWhitespace = document.getElementById('trim-whitespace');
// File handling
let file1Data = null;
let file2Data = null;
let headers = [];
let commonRows = [];
// Setup drag and drop for file inputs
setupDragDrop(file1Input, document.querySelector('label[for="file1"]'));
setupDragDrop(file2Input, document.querySelector('label[for="file2"]'));
// File input change handlers
file1Input.addEventListener('change', function(e) {
handleFileSelect(e, file1Name, 1);
});
file2Input.addEventListener('change', function(e) {
handleFileSelect(e, file2Name, 2);
});
// Compare button click handler
compareBtn.addEventListener('click', compareFiles);
// Export button click handler
exportBtn.addEventListener('click', exportResults);
// Functions
function setupDragDrop(inputElement, labelElement) {
labelElement.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('drag-over');
});
labelElement.addEventListener('dragleave', function(e) {
e.preventDefault();
this.classList.remove('drag-over');
});
labelElement.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('drag-over');
if (e.dataTransfer.files.length) {
inputElement.files = e.dataTransfer.files;
const event = new Event('change');
inputElement.dispatchEvent(event);
}
});
}
function handleFileSelect(event, nameElement, fileNumber) {
const file = event.target.files[0];
if (!file) return;
// Update UI with file name
nameElement.textContent = file.name;
nameElement.title = file.name;
// Read the file
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
// Store the data
if (fileNumber === 1) {
file1Data = jsonData;
} else {
file2Data = jsonData;
}
// Enable compare button if both files are loaded
if (file1Data && file2Data) {
compareBtn.disabled = false;
}
} catch (error) {
showError(`Error reading file ${fileNumber}: ${error.message}`);
if (fileNumber === 1) {
file1Name.textContent = '';
file1Data = null;
} else {
file2Name.textContent = '';
file2Data = null;
}
compareBtn.disabled = true;
}
};
reader.onerror = function() {
showError(`Error reading file ${fileNumber}`);
if (fileNumber === 1) {
file1Name.textContent = '';
file1Data = null;
} else {
file2Name.textContent = '';
file2Data = null;
}
compareBtn.disabled = true;
};
reader.readAsArrayBuffer(file);
}
function compareFiles() {
// Show loading state
resultsSection.classList.remove('hidden');
loadingIndicator.classList.remove('hidden');
resultsContainer.classList.add('hidden');
noResults.classList.add('hidden');
stats.classList.add('hidden');
compareBtn.disabled = true;
compareBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Processing...';
// Use setTimeout to allow UI to update before heavy processing
setTimeout(() => {
const startTime = performance.now();
try {
// Process the data
headers = file1Data[0] || [];
// Get rows (skip headers)
const rows1 = file1Data.slice(1);
const rows2 = file2Data.slice(1);
// Update stats
file1Rows.textContent = rows1.length;
file2Rows.textContent = rows2.length;
// Preprocess rows based on options
const processedRows1 = preprocessRows(rows1);
const processedRows2 = preprocessRows(rows2);
// Find common rows
commonRows = findCommonRows(processedRows1, processedRows2);
// Update UI with results
updateResultsUI(commonRows);
// Update stats
const endTime = performance.now();
processingTime.textContent = `${Math.round(endTime - startTime)}ms`;
stats.classList.remove('hidden');
} catch (error) {
showError(`Error during comparison: ${error.message}`);
} finally {
// Reset loading state
loadingIndicator.classList.add('hidden');
compareBtn.disabled = false;
compareBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Compare Files';
}
}, 100);
}
function preprocessRows(rows) {
return rows.map(row => {
return row.map(cell => {
let value = cell === undefined || cell === null ? '' : String(cell);
if (trimWhitespace.checked) {
value = value.trim();
}
if (ignoreCase.checked) {
value = value.toLowerCase();
}
return value;
});
});
}
function findCommonRows(rows1, rows2) {
// Convert rows to strings for easy comparison
const rowStrings1 = rows1.map(row => JSON.stringify(row));
const rowStrings2 = rows2.map(row => JSON.stringify(row));
// Create sets for faster lookup
const set1 = new Set(rowStrings1);
const set2 = new Set(rowStrings2);
// Find intersection
const commonStrings = [...set1].filter(rowStr => set2.has(rowStr));
// Convert back to original rows (from rows1)
return rows1.filter(row => commonStrings.includes(JSON.stringify(row)));
}
function updateResultsUI(commonRows) {
if (commonRows.length === 0) {
noResults.classList.remove('hidden');
resultsContainer.classList.add('hidden');
matchCount.textContent = '0 matches';
return;
}
// Update match count
matchCount.textContent = `${commonRows.length} ${commonRows.length === 1 ? 'match' : 'matches'}`;
// Clear previous results
tableHeaders.innerHTML = '';
tableBody.innerHTML = '';
// Add headers
headers.forEach(header => {
const th = document.createElement('th');
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
th.textContent = header || 'Column';
tableHeaders.appendChild(th);
});
// Add rows
commonRows.forEach((row, rowIndex) => {
const tr = document.createElement('tr');
tr.className = rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50';
row.forEach((cell, cellIndex) => {
const td = document.createElement('td');
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
td.textContent = cell;
// Highlight header cells if this is the first row
if (rowIndex === 0 && cellIndex < headers.length && cell === headers[cellIndex]) {
td.className += ' font-semibold text-indigo-600';
}
tr.appendChild(td);
});
tableBody.appendChild(tr);
});
resultsContainer.classList.remove('hidden');
noResults.classList.add('hidden');
}
function exportResults() {
if (!commonRows.length) {
showError('No results to export');
return;
}
try {
// Create a new workbook
const wb = XLSX.utils.book_new();
// Add headers to the data
const exportData = [headers, ...commonRows];
// Create a worksheet
const ws = XLSX.utils.aoa_to_sheet(exportData);
// Add the worksheet to the workbook
XLSX.utils.book_append_sheet(wb, ws, "Matching Rows");
// Generate the file and trigger download
XLSX.writeFile(wb, 'matching_rows.xlsx');
} catch (error) {
showError(`Error exporting results: ${error.message}`);
}
}
function showError(message) {
// In a real app, you'd want a more sophisticated error display
alert(`Error: ${message}`);
}
});
</script>
<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>
</html>