160 lines
6.6 KiB
Python
160 lines
6.6 KiB
Python
import tkinter as tk
|
||
from tkinter import filedialog, messagebox
|
||
import csv
|
||
import os
|
||
|
||
# --- Основная функция конвертации ---
|
||
def convert_csv_to_html(csv_file_path, html_file_path):
|
||
"""
|
||
Читает semicolon-separated CSV файл и создает красивую HTML-страницу.
|
||
Возвращает (True, сообщение) в случае успеха или (False, сообщение) в случае ошибки.
|
||
"""
|
||
try:
|
||
with open(csv_file_path, 'r', encoding='utf-8') as f:
|
||
reader = csv.DictReader(f, delimiter=';')
|
||
headers = reader.fieldnames
|
||
if not headers:
|
||
return False, "CSV-файл пуст или не имеет заголовков."
|
||
|
||
rows = list(reader)
|
||
|
||
except FileNotFoundError:
|
||
return False, f"Файл не найден: {csv_file_path}"
|
||
except Exception as e:
|
||
return False, f"Ошибка при чтении CSV: {e}"
|
||
|
||
# CSS для красивого оформления
|
||
css_style = """
|
||
<style>
|
||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: #f4f7f6; color: #333; margin: 0; padding: 20px; }
|
||
.container { max-width: 1400px; margin: 20px auto; background-color: #fff; padding: 25px; border-radius: 8px; box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); }
|
||
h1 { color: #2c3e50; text-align: center; margin-bottom: 25px; }
|
||
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e0e0e0; vertical-align: top; word-wrap: break-word; }
|
||
th { background-color: #34495e; color: #ffffff; font-weight: 600; text-transform: uppercase; font-size: 13px; }
|
||
tr:nth-child(even) { background-color: #f8f9fa; }
|
||
tr:hover { background-color: #e9ecef; }
|
||
a { color: #3498db; text-decoration: none; font-weight: bold; }
|
||
a:hover { text-decoration: underline; color: #2980b9; }
|
||
.mono { font-family: 'Courier New', Courier, monospace; font-size: 13px; white-space: pre-wrap; }
|
||
tr:target { background-color: #fff3cd !important; border: 2px solid #ffeaa7; animation: highlight 2s ease-in-out; }
|
||
@keyframes highlight { from { background-color: #ffeaa7; } to { background-color: #fff3cd; } }
|
||
</style>
|
||
"""
|
||
|
||
# Начало HTML-документа
|
||
html_content = f"""
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{os.path.basename(html_file_path)}</title>
|
||
{css_style}
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Анализ дизассемблерного листинга: {os.path.basename(csv_file_path)}</h1>
|
||
<table>
|
||
<thead><tr>
|
||
"""
|
||
for header in headers:
|
||
html_content += f"<th>{header}</th>\n"
|
||
|
||
html_content += "</tr></thead><tbody>\n"
|
||
|
||
# Добавляем строки таблицы
|
||
for row in rows:
|
||
address = row.get('адрес', '').strip()
|
||
target_address = row.get('целевой_адрес', '').strip()
|
||
|
||
link_html = target_address
|
||
if target_address:
|
||
link_html = f'<a href="#{target_address}">{target_address}</a>'
|
||
|
||
# Присваиваем id каждой строке на основе ее адреса
|
||
row_id = address if address else ''
|
||
|
||
html_content += f'<tr id="{row_id}">\n'
|
||
html_content += f'<td class="mono">{address}</td>\n'
|
||
html_content += f'<td class="mono">{row.get("байт", "")}</td>\n'
|
||
html_content += f'<td class="mono">{link_html}</td>\n'
|
||
html_content += f'<td>{row.get("исходная_команда", "")}</td>\n'
|
||
html_content += "</tr>\n"
|
||
|
||
html_content += "</tbody></table></div></body></html>"
|
||
|
||
# Запись HTML в файл
|
||
try:
|
||
with open(html_file_path, 'w', encoding='utf-8') as f:
|
||
f.write(html_content)
|
||
return True, f"Успешно! Файл сохранен как:\n{html_file_path}"
|
||
except Exception as e:
|
||
return False, f"Ошибка при записи HTML файла: {e}"
|
||
|
||
|
||
# --- Функция-обработчик нажатия на кнопку ---
|
||
def open_file_and_convert():
|
||
# Открываем диалог выбора файла
|
||
file_path = filedialog.askopenfilename(
|
||
title="Выберите CSV-файл для конвертации",
|
||
filetypes=(("CSV файлы", "*.csv"), ("Все файлы", "*.*"))
|
||
)
|
||
|
||
# Если пользователь не выбрал файл, выходим
|
||
if not file_path:
|
||
status_label.config(text="Операция отменена.")
|
||
return
|
||
|
||
# Формируем имя для выходного HTML-файла
|
||
base_name = os.path.splitext(file_path)[0]
|
||
output_path = f"{base_name}.html"
|
||
|
||
# Обновляем статус
|
||
status_label.config(text="Обработка файла...")
|
||
root.update_idletasks() # Принудительно обновляем интерфейс
|
||
|
||
# Вызываем функцию конвертации
|
||
success, message = convert_csv_to_html(file_path, output_path)
|
||
|
||
# Показываем результат
|
||
if success:
|
||
status_label.config(text="Готово!")
|
||
messagebox.showinfo("Успех", message)
|
||
else:
|
||
status_label.config(text="Произошла ошибка.")
|
||
messagebox.showerror("Ошибка", message)
|
||
|
||
|
||
# --- Создание графического интерфейса ---
|
||
root = tk.Tk()
|
||
root.title("CSV в HTML Конвертер")
|
||
root.geometry("450x200") # Размер окна
|
||
root.resizable(False, False) # Запрещаем изменять размер
|
||
|
||
# Создаем фрейм для лучшего расположения элементов
|
||
main_frame = tk.Frame(root, padx=20, pady=20)
|
||
main_frame.pack(expand=True, fill=tk.BOTH)
|
||
|
||
# Кнопка "Открыть"
|
||
open_button = tk.Button(
|
||
main_frame,
|
||
text="Открыть CSV-файл",
|
||
command=open_file_and_convert,
|
||
width=20,
|
||
height=2,
|
||
font=("Helvetica", 12)
|
||
)
|
||
open_button.pack(pady=10)
|
||
|
||
# Метка для отображения статуса
|
||
status_label = tk.Label(
|
||
main_frame,
|
||
text="Нажмите кнопку, чтобы выбрать файл",
|
||
wraplength=400, # Перенос длинных строк
|
||
justify=tk.CENTER
|
||
)
|
||
status_label.pack(pady=10)
|
||
|
||
# Запускаем главный цикл приложения
|
||
root.mainloop() |