Рефакторинг проекта: разделение на модули, улучшенный интерфейс, обработка Excel

This commit is contained in:
2025-08-04 00:31:05 +05:00
parent 059f174dd5
commit a882a81180
22 changed files with 1936 additions and 530 deletions

402
main.py
View File

@@ -1,39 +1,403 @@
import tkinter as tk
from tkinter import filedialog
from tkinter import filedialog, messagebox, ttk
import os
import threading
from pathlib import Path
# Импорт наших сервисов
from services.file_seeker import FileSeeker
from services.excel_processor import ExcelProcessor
def copy_matching_files(matching_numbers, cadastral_to_file_map, target_directory):
"""Копирует файлы с совпадающими кадастровыми номерами в целевую директорию"""
import shutil
moved_count = 0
for number in matching_numbers:
if number in cadastral_to_file_map:
source_path = Path(cadastral_to_file_map[number])
if source_path.exists():
# Создаем уникальное имя файла
dest_name = f"{number.replace(':', '_')}_{source_path.name}"
dest_path = target_directory / dest_name
try:
shutil.copy2(source_path, dest_path)
moved_count += 1
print(f"Файл скопирован в matching: {source_path.name} -> {dest_path}")
except Exception as e:
print(f"Ошибка при копировании файла {source_path}: {e}")
return moved_count
def Pars():
pass
"""Основная функция обработки"""
xml_directory = xml_entry.get().strip()
excel_file_path = excel_entry.get().strip()
# Создаем директории вывода рядом с программой
# Для PyInstaller нужно использовать sys.executable вместо __file__
import sys
if getattr(sys, 'frozen', False):
# Если программа скомпилирована PyInstaller
script_dir = Path(sys.executable).parent
else:
# Если запускается как Python скрипт
script_dir = Path(__file__).parent
output_directory = script_dir / "results"
print(f"Создаем директорию результатов: {output_directory}") # Отладочный вывод
output_directory.mkdir(exist_ok=True)
# Создаем две отдельные папки для организации файлов
all_files_dir = output_directory / "all_xml_files" # Все найденные XML файлы
matching_files_dir = output_directory / "matching_cadastral_files" # Файлы с номерами из Excel
all_files_dir.mkdir(exist_ok=True)
matching_files_dir.mkdir(exist_ok=True)
# Проверяем входные данные
if not xml_directory or not os.path.exists(xml_directory):
messagebox.showerror("Ошибка", "Выберите существующую директорию с XML файлами")
return
# Запускаем обработку в отдельном потоке
def process_files():
try:
# Показываем прогресс
progress_window = show_progress_window()
# 1. Инициализируем FileSeeker с первой папкой для всех файлов
update_progress(progress_window, "Поиск XML файлов...", 10)
seeker = FileSeeker(xml_directory, str(all_files_dir))
# 2. Ищем XML файлы
update_progress(progress_window, "Анализ найденных файлов...", 30)
xml_files = seeker.find_xml_files()
if not xml_files:
progress_window.destroy()
messagebox.showwarning("Предупреждение", "XML файлы не найдены в указанной директории")
return
# 3. Обрабатываем файлы
update_progress(progress_window, "Извлечение кадастровых номеров...", 50)
records = seeker.process_files()
if not records:
progress_window.destroy()
messagebox.showwarning("Предупреждение", "Кадастровые номера не найдены в XML файлах")
return
# 4. Создаем карту номер -> файл для быстрого поиска
cadastral_to_file_map = {}
file_to_numbers_map = {} # файл -> множество номеров для поиска смежных
for record in records:
# Для основной карты берем первое вхождение номера
if record.number not in cadastral_to_file_map:
cadastral_to_file_map[record.number] = record.file_path
# Для карты файл->номера добавляем все номера из каждого файла
if record.file_path not in file_to_numbers_map:
file_to_numbers_map[record.file_path] = set()
file_to_numbers_map[record.file_path].add(record.number)
# 5. Обработка Excel файла и организация файлов
excel_updated = False
matching_files_moved = 0
if excel_file_path and os.path.exists(excel_file_path):
update_progress(progress_window, "Обработка Excel файла...", 60)
try:
excel_processor = ExcelProcessor(excel_file_path)
if excel_processor.load_excel_file():
excel_numbers = excel_processor.get_cadastral_numbers()
# Находим пересечения между Excel и найденными номерами
found_numbers = set(cadastral_to_file_map.keys())
matching_numbers = excel_numbers.intersection(found_numbers)
if matching_numbers:
# Обновляем Excel файл информацией о найденных файлах
output_excel_path = str(output_directory / "updated_cadastral_list.xlsx")
excel_processor.update_excel_with_file_info(cadastral_to_file_map, output_excel_path)
excel_updated = True
# Копируем файлы с номерами из Excel в отдельную папку
update_progress(progress_window, "Организация файлов из Excel списка...", 70)
matching_files_moved = copy_matching_files(matching_numbers, cadastral_to_file_map, matching_files_dir)
# Копируем ВСЕ найденные файлы в общую папку
update_progress(progress_window, "Копирование всех найденных файлов...", 80)
all_files_moved = seeker.move_files_to_temp(set(cadastral_to_file_map.keys()))
else:
# Если совпадений нет, копируем все найденные файлы только в общую папку
update_progress(progress_window, "Организация всех найденных файлов...", 75)
all_files_moved = seeker.move_files_to_temp(set(cadastral_to_file_map.keys()))
else:
# Если Excel не загрузился, обрабатываем все файлы
update_progress(progress_window, "Организация всех найденных файлов...", 75)
all_files_moved = seeker.move_files_to_temp(set(cadastral_to_file_map.keys()))
except Exception as excel_error:
print(f"Ошибка при обработке Excel: {excel_error}")
update_progress(progress_window, "Организация всех найденных файлов...", 75)
all_files_moved = seeker.move_files_to_temp(set(cadastral_to_file_map.keys()))
else:
# Если Excel файл не указан, копируем все найденные файлы только в общую папку
update_progress(progress_window, "Организация всех найденных файлов...", 75)
all_files_moved = seeker.move_files_to_temp(set(cadastral_to_file_map.keys()))
# 6. Получаем статистику
update_progress(progress_window, "Подготовка результатов...", 85)
stats = seeker.get_statistics()
unique_numbers = seeker.get_unique_cadastral_numbers()
# Подготавливаем информацию о перемещенных файлах для отчета
if 'all_files_moved' in locals():
moved_files_info = all_files_moved
total_moved = len(all_files_moved)
else:
moved_files_info = {}
total_moved = 0
# 7. Создаем отчет
update_progress(progress_window, "Создание отчета...", 95)
create_report(str(output_directory), stats, unique_numbers, moved_files_info, excel_updated, excel_file_path, matching_files_moved if 'matching_files_moved' in locals() else 0)
# Закрываем окно прогресса
progress_window.destroy()
# Показываем результаты
show_results(stats, len(unique_numbers), total_moved, excel_updated, matching_files_moved if 'matching_files_moved' in locals() else 0)
except Exception as e:
if 'progress_window' in locals():
progress_window.destroy()
messagebox.showerror("Ошибка", f"Произошла ошибка при обработке: {str(e)}")
# Запускаем в отдельном потоке
thread = threading.Thread(target=process_files)
thread.daemon = True
thread.start()
def show_progress_window():
"""Создает окно с прогресс-баром"""
progress_window = tk.Toplevel(root)
progress_window.title("Обработка файлов")
progress_window.geometry("400x150")
progress_window.transient(root)
progress_window.grab_set()
# Центрируем окно
progress_window.geometry("+%d+%d" % (root.winfo_rootx() + 50, root.winfo_rooty() + 50))
# Метка с текстом
progress_label = tk.Label(progress_window, text="Инициализация...", font=font)
progress_label.pack(pady=20)
# Прогресс-бар
progress_bar = ttk.Progressbar(progress_window, length=300, mode='determinate')
progress_bar.pack(pady=10)
progress_window.label = progress_label
progress_window.bar = progress_bar
return progress_window
def update_progress(progress_window, text, value):
"""Обновляет прогресс-бар"""
if progress_window and progress_window.winfo_exists():
progress_window.label.config(text=text)
progress_window.bar['value'] = value
progress_window.update()
def create_report(output_directory, stats, unique_numbers, moved_files, excel_updated=False, excel_file_path=None, matching_files_count=0):
"""Создает текстовый отчет о результатах обработки"""
report_path = os.path.join(output_directory, "processing_report.txt")
with open(report_path, 'w', encoding='utf-8') as f:
f.write("=== ОТЧЕТ О ОБРАБОТКЕ XML ФАЙЛОВ ===\n\n")
f.write(f"Общая статистика:\n")
f.write(f"- Всего XML файлов найдено: {stats['total_xml_files']}\n")
f.write(f"- Файлов с кадастровыми номерами: {stats['files_with_numbers']}\n")
f.write(f"- Всего кадастровых номеров: {stats['total_cadastral_numbers']}\n")
f.write(f"- Уникальных номеров: {stats['unique_cadastral_numbers']}\n")
f.write(f"- Файлов скопировано в 'all_xml_files': {len(moved_files)}\n")
if matching_files_count > 0:
f.write(f"- Файлов из Excel списка скопировано в 'matching_cadastral_files': {matching_files_count}\n")
f.write("\n📁 Структура результатов:\n")
f.write("- all_xml_files/ - все найденные XML файлы с кадастровыми номерами\n")
f.write("- matching_cadastral_files/ - файлы с номерами из Excel списка\n")
f.write("- processing_report.txt - данный отчет\n")
if excel_updated and excel_file_path:
f.write(f"- updated_cadastral_list.xlsx - обновленный Excel файл\n\n")
f.write(f"Excel обработка:\n")
f.write(f"- Исходный Excel файл: {Path(excel_file_path).name}\n")
f.write(f"- Обновленный файл создан: updated_cadastral_list.xlsx\n")
f.write(f"- Добавлены колонки:\n")
f.write(f"'Найденные файлы' - файлы с кадастровыми номерами\n")
f.write(f"'Смежные кадастровые номера' - другие номера из тех же файлов\n")
f.write(f"'Кадастровый блок' - первые 3 части номера\n\n")
else:
f.write("\n")
f.write("Найденные кадастровые номера:\n")
for number in sorted(unique_numbers):
f.write(f"- {number}\n")
if moved_files:
f.write(f"\nСкопированные файлы:\n")
for number, file_path in moved_files.items():
f.write(f"- {number}: {Path(file_path).name}\n")
def show_results(stats, unique_count, total_moved, excel_updated=False, matching_files_count=0):
"""Показывает окно с результатами обработки"""
excel_info = ""
if excel_updated:
excel_info = ("\n📊 Excel файл обновлен с дополнительной информацией:\n"
"• Файлы с найденными номерами\n"
"• Смежные кадастровые номера из тех же файлов\n"
"• Кадастровые блоки")
matching_info = ""
if matching_files_count > 0:
matching_info = f"\n📋 Файлов из Excel списка: {matching_files_count}"
result_text = (
f"✅ Обработка завершена успешно!\n\n"
f"📊 Результаты:\n"
f"• XML файлов обработано: {stats['total_xml_files']}\n"
f"• Файлов с номерами: {stats['files_with_numbers']}\n"
f"• Уникальных кадастровых номеров: {unique_count}\n"
f"Всего файлов скопировано: {total_moved}"
f"{matching_info}\n\n"
f"📁 Результаты сохранены в папке 'results':\n"
f"• all_xml_files/ - все найденные файлы\n"
f"• matching_cadastral_files/ - файлы из Excel\n"
f"📋 Создан подробный отчет: processing_report.txt"
f"{excel_info}"
)
messagebox.showinfo("Результаты обработки", result_text)
root = tk.Tk()
root.title("Parser12")
root.title("Parser14")
font = ("Arial", 12)
root.configure(bg="#B85C38")
options = [""]
root.geometry("600x500")
# Устанавливаем минимальный размер окна
root.minsize(500, 250)
#путь для сохранения файлов
def open_directory(event, entry):
directory_path = filedialog.askdirectory()
entry.delete(0, tk.END)
entry.insert(0, directory_path)
if directory_path: # Проверяем что путь выбран
entry.delete(0, tk.END)
entry.insert(0, directory_path)
# Функция для выбора Excel файла
def open_excel_file(event, entry):
file_path = filedialog.askopenfilename(
title="Выберите Excel файл",
filetypes=[("Excel files", "*.xlsx *.xls"), ("All files", "*.*")]
)
if file_path:
entry.delete(0, tk.END)
entry.insert(0, file_path)
# Создаем главный фрейм
main_frame = tk.Frame(root, bg="#B85C38")
main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
# Заголовок
title_label = tk.Label(main_frame,
text="🏗️ Парсер кадастровых номеров из XML файлов",
font=("Arial", 14, "bold"),
bg="#B85C38",
fg="white")
title_label.pack(pady=(0, 20))
# первое поле с списком
xml_label = tk.Label(root, text="директория xml", font=font)
xml_label.pack(anchor="w")
xml_entry = tk.Entry(root, font=font, width=70)
xml_entry.pack(anchor="w")
xml_label = tk.Label(main_frame, text="📁 Директория с XML файлами:", font=font, bg="#B85C38", fg="white")
xml_label.pack(anchor="w", pady=(0, 5))
xml_entry = tk.Entry(main_frame, font=font, width=70)
xml_entry.pack(anchor="w", fill=tk.X, pady=(0, 15))
xml_entry.bind("<Button-1>", lambda event: open_directory(event, xml_entry))
directory_label = tk.Label(root, text="Выберите директорию для сохранения:", font=font)
directory_label.pack(anchor="w")
directory_entry = tk.Entry(root, font=font, width=70)
directory_entry.pack(anchor="w")
directory_entry.bind("<Button-1>", lambda event: open_directory(event, directory_entry))
# Поле для Excel файла (опционально)
excel_label = tk.Label(main_frame, text="📊 Excel файл со списком номеров (опционально):", font=font, bg="#B85C38", fg="white")
excel_label.pack(anchor="w", pady=(0, 5))
excel_entry = tk.Entry(main_frame, font=font, width=70)
excel_entry.pack(anchor="w", fill=tk.X, pady=(0, 15))
excel_entry.bind("<Button-1>", lambda event: open_excel_file(event, excel_entry))
# кнопка
empty_button = tk.Button(root, text="->", command=Pars, font=font)
empty_button.pack(side="right", padx=10, pady=10)
# Информация о месте сохранения
info_label = tk.Label(main_frame,
text="💾 Результаты будут сохранены в папке 'results' рядом с программой",
font=("Arial", 10),
bg="#B85C38",
fg="#FFE4B5")
info_label.pack(anchor="w", pady=(0, 20))
# Фрейм для кнопок
button_frame = tk.Frame(main_frame, bg="#B85C38")
button_frame.pack(fill=tk.X)
# кнопка запуска
start_button = tk.Button(button_frame,
text="🚀 Начать обработку",
command=Pars,
font=("Arial", 12, "bold"),
bg="#2E8B57",
fg="white",
padx=20,
pady=10)
start_button.pack(side="right", padx=(10, 0))
# Кнопка помощи
help_button = tk.Button(button_frame,
text=" Справка",
command=lambda: show_help(),
font=font,
bg="#4682B4",
fg="white",
padx=15,
pady=10)
help_button.pack(side="right")
def show_help():
"""Показывает справочную информацию"""
help_text = """
🔍 Инструкция по использованию:
1. Выберите директорию с XML файлами
2. (Опционально) Выберите Excel файл со списком кадастровых номеров
3. Нажмите "Начать обработку"
📋 Программа выполнит:
• Поиск всех XML файлов в указанной директории
• Извлечение кадастровых номеров из файлов
• Организацию файлов по номерам
• Обновление Excel файла (если указан) с дополнительными колонками:
- Найденные файлы (где обнаружен номер)
- Смежные кадастровые номера (из тех же файлов)
- Кадастровый блок (первые 3 части номера)
• Создание подробного отчета
📁 Результаты сохраняются в папке 'results' рядом с программой:
• all_xml_files/ - ВСЕ найденные XML файлы с кадастровыми номерами
• matching_cadastral_files/ - файлы с номерами из Excel списка
• updated_cadastral_list.xlsx - обновленный Excel (если указан)
• processing_report.txt - подробный отчет
⚡ Поддерживаемые форматы номеров: XX:XX:XXXXXXX:XXX
"""
messagebox.showinfo("Справка", help_text)
root.mainloop()