diff --git a/data_comparator.py b/data_comparator.py
index 968e200..cf9a37b 100644
--- a/data_comparator.py
+++ b/data_comparator.py
@@ -646,6 +646,33 @@ class KSTCoordiComparator:
return visualize_rows
+ def generate_excel_export_data(self) -> Dict[str, List[Dict[str, Any]]]:
+ """Generate data for Excel export with all sheets in visualize format"""
+ export_data = {}
+
+ # Get all sheet names
+ sheet_names = list(self.data.keys()) if self.data else []
+
+ for sheet_name in sheet_names:
+ # Generate visualize data for each sheet
+ sheet_visualize_data = self.generate_visualize_data(sheet_name)
+
+ # Convert to Excel-friendly format
+ excel_rows = []
+ for row in sheet_visualize_data:
+ excel_rows.append({
+ 'Coordi Title': row.get('coordi_title', ''),
+ 'Coordi Chapter': row.get('coordi_chapter', ''),
+ 'KST Title': row.get('kst_title', ''),
+ 'KST Chapter': row.get('kst_chapter', ''),
+ 'Status': row.get('reason', ''),
+ 'Type': row.get('row_type', '').replace('_', ' ').title()
+ })
+
+ export_data[sheet_name] = excel_rows
+
+ return export_data
+
def print_comparison_summary(self, sheet_filter: str | None = None):
"""Print a formatted summary of the comparison for a specific sheet"""
diff --git a/templates/index.html b/templates/index.html
index 4c437d2..1c31b45 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -284,7 +284,10 @@
-
Data
+
+
Excel-like Visualization
+
+
@@ -670,6 +673,60 @@
});
}
+ function downloadExcel() {
+ const downloadBtn = document.getElementById('downloadBtn');
+ const originalText = downloadBtn.textContent;
+
+ // Show loading state
+ downloadBtn.disabled = true;
+ downloadBtn.textContent = '⏳ Generating...';
+ downloadBtn.style.background = '#6c757d';
+
+ // Create a temporary link and trigger download
+ fetch('/download_excel', {
+ method: 'GET'
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Download failed');
+ }
+ return response.blob();
+ })
+ .then(blob => {
+ // Create download link
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.style.display = 'none';
+ a.href = url;
+ a.download = 'data_comparison_export.xlsx';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+
+ // Show success message
+ const statusDiv = document.getElementById('status');
+ statusDiv.innerHTML = 'Excel file downloaded successfully!
';
+ setTimeout(() => {
+ statusDiv.innerHTML = '';
+ }, 3000);
+ })
+ .catch(error => {
+ console.error('Download error:', error);
+ const statusDiv = document.getElementById('status');
+ statusDiv.innerHTML = 'Download failed. Please try again.
';
+ setTimeout(() => {
+ statusDiv.innerHTML = '';
+ }, 5000);
+ })
+ .finally(() => {
+ // Reset button state
+ downloadBtn.disabled = false;
+ downloadBtn.textContent = originalText;
+ downloadBtn.style.background = '#28a745';
+ });
+ }
+
// Auto-analyze on page load with default file
window.onload = function() {
// Initialize sheet filter with loading state
diff --git a/web_gui.py b/web_gui.py
index 0e5a547..d4b9d01 100644
--- a/web_gui.py
+++ b/web_gui.py
@@ -1,7 +1,8 @@
-from flask import Flask, render_template, request, jsonify
+from flask import Flask, render_template, request, jsonify, send_file
import json
import os
import tempfile
+import pandas as pd
from pathlib import Path
from werkzeug.utils import secure_filename
from data_comparator import KSTCoordiComparator
@@ -131,6 +132,99 @@ def get_sheets():
except Exception as e:
return jsonify({'error': str(e)}), 500
+@app.route('/download_excel')
+def download_excel():
+ """Generate and download Excel file with all sheets in visualize format"""
+ global comparator_instance
+
+ try:
+ if not comparator_instance:
+ return jsonify({'error': 'No data available. Please analyze data first.'}), 400
+
+ # Generate export data for all sheets
+ export_data = comparator_instance.generate_excel_export_data()
+
+ if not export_data:
+ return jsonify({'error': 'No data available for export'}), 400
+
+ # Create temporary Excel file
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx')
+ temp_path = temp_file.name
+ temp_file.close()
+
+ try:
+ # Create Excel writer with multiple sheets
+ with pd.ExcelWriter(temp_path, engine='openpyxl') as writer:
+ for sheet_name, sheet_data in export_data.items():
+ if sheet_data: # Only create sheet if there's data
+ df = pd.DataFrame(sheet_data)
+
+ # Clean sheet name for Excel (remove invalid characters)
+ clean_sheet_name = str(sheet_name).replace('/', '_').replace('\\', '_')[:31]
+
+ df.to_excel(writer, sheet_name=clean_sheet_name, index=False)
+
+ # Get the workbook and worksheet to apply formatting
+ workbook = writer.book
+ worksheet = writer.sheets[clean_sheet_name]
+
+ # Apply color formatting based on Type column
+ from openpyxl.styles import PatternFill
+
+ # Define colors matching the web interface
+ colors = {
+ 'Coordi Only': PatternFill(start_color='FF4444', end_color='FF4444', fill_type='solid'),
+ 'Kst Only': PatternFill(start_color='4488FF', end_color='4488FF', fill_type='solid'),
+ 'Mixed Duplicate': PatternFill(start_color='FF8800', end_color='FF8800', fill_type='solid'),
+ 'Pure Duplicate': PatternFill(start_color='8844FF', end_color='8844FF', fill_type='solid'),
+ 'Matched': PatternFill(start_color='FFFFFF', end_color='FFFFFF', fill_type='solid')
+ }
+
+ # Find the Type column (should be column F, index 5)
+ type_col_idx = None
+ for idx, col in enumerate(df.columns):
+ if col == 'Type':
+ type_col_idx = idx + 1 # Excel is 1-indexed
+ break
+
+ # Apply formatting to data rows (skip header)
+ if type_col_idx:
+ for row_idx, row_data in enumerate(sheet_data, start=2): # Start from row 2 (after header)
+ row_type = row_data.get('Type', '')
+ fill = colors.get(row_type)
+ if fill:
+ for col_idx in range(1, len(df.columns) + 1):
+ cell = worksheet.cell(row=row_idx, column=col_idx)
+ cell.fill = fill
+
+ # Auto-adjust column widths
+ for column in worksheet.columns:
+ max_length = 0
+ column_letter = column[0].column_letter
+ for cell in column:
+ try:
+ if len(str(cell.value)) > max_length:
+ max_length = len(str(cell.value))
+ except:
+ pass
+ adjusted_width = min(max_length + 2, 50) # Cap at 50 characters
+ worksheet.column_dimensions[column_letter].width = adjusted_width
+
+ # Send file for download
+ return send_file(
+ temp_path,
+ as_attachment=True,
+ download_name='data_comparison_export.xlsx',
+ mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ )
+
+ finally:
+ # Clean up temporary file after a delay (Flask handles this)
+ pass
+
+ except Exception as e:
+ return jsonify({'error': f'Export failed: {str(e)}'}), 500
+
def create_templates_dir():
"""Create templates directory and HTML file"""
templates_dir = Path('templates')
@@ -422,7 +516,10 @@ def create_templates_dir():
-
Data
+
+
Excel-like Visualization
+
+
@@ -808,6 +905,60 @@ def create_templates_dir():
});
}
+ function downloadExcel() {
+ const downloadBtn = document.getElementById('downloadBtn');
+ const originalText = downloadBtn.textContent;
+
+ // Show loading state
+ downloadBtn.disabled = true;
+ downloadBtn.textContent = '⏳ Generating...';
+ downloadBtn.style.background = '#6c757d';
+
+ // Create a temporary link and trigger download
+ fetch('/download_excel', {
+ method: 'GET'
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Download failed');
+ }
+ return response.blob();
+ })
+ .then(blob => {
+ // Create download link
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.style.display = 'none';
+ a.href = url;
+ a.download = 'data_comparison_export.xlsx';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+
+ // Show success message
+ const statusDiv = document.getElementById('status');
+ statusDiv.innerHTML = 'Excel file downloaded successfully!
';
+ setTimeout(() => {
+ statusDiv.innerHTML = '';
+ }, 3000);
+ })
+ .catch(error => {
+ console.error('Download error:', error);
+ const statusDiv = document.getElementById('status');
+ statusDiv.innerHTML = 'Download failed. Please try again.
';
+ setTimeout(() => {
+ statusDiv.innerHTML = '';
+ }, 5000);
+ })
+ .finally(() => {
+ // Reset button state
+ downloadBtn.disabled = false;
+ downloadBtn.textContent = originalText;
+ downloadBtn.style.background = '#28a745';
+ });
+ }
+
// Auto-analyze on page load with default file
window.onload = function() {
// Initialize sheet filter with loading state