401 lines
21 KiB
Python
401 lines
21 KiB
Python
import tkinter as tk
|
||
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():
|
||
"""Основная функция обработки"""
|
||
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 = {}
|
||
|
||
for record in records:
|
||
# Добавляем все номера из объекта в карту
|
||
for number in record.all_numbers:
|
||
if number not in cadastral_to_file_map: # Берем первый найденный файл
|
||
cadastral_to_file_map[number] = record.file_path
|
||
|
||
# 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 и найденными номерами
|
||
# Теперь ищем по всем номерам в объектах, не только по основным
|
||
all_found_numbers = seeker.get_all_cadastral_numbers()
|
||
matching_numbers = excel_numbers.intersection(all_found_numbers)
|
||
|
||
if matching_numbers:
|
||
# Обновляем Excel файл информацией о найденных объектах
|
||
output_excel_path = str(output_directory / "updated_cadastral_list.xlsx")
|
||
excel_processor.update_excel_with_cadastral_records(records, 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_objects']}\n")
|
||
f.write(f"- Уникальных основных номеров: {stats['unique_main_numbers']}\n")
|
||
f.write(f"- Всего номеров во всех объектах: {stats['total_all_numbers']}\n")
|
||
f.write(f"- Всего уникальных номеров: {stats['unique_all_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("Parser14")
|
||
font = ("Arial", 12)
|
||
root.configure(bg="#B85C38")
|
||
root.geometry("600x500")
|
||
|
||
# Устанавливаем минимальный размер окна
|
||
root.minsize(500, 250)
|
||
|
||
#путь для сохранения файлов
|
||
def open_directory(event, entry):
|
||
directory_path = filedialog.askdirectory()
|
||
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(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))
|
||
|
||
# Поле для 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))
|
||
|
||
# Информация о месте сохранения
|
||
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() |