xml
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
kadastr14_temp/
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
39
main.py
Normal file
39
main.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
def Pars():
|
||||||
|
pass
|
||||||
|
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Parser12")
|
||||||
|
font = ("Arial", 12)
|
||||||
|
root.configure(bg="#B85C38")
|
||||||
|
options = [""]
|
||||||
|
|
||||||
|
#путь для сохранения файлов
|
||||||
|
def open_directory(event, entry):
|
||||||
|
directory_path = filedialog.askdirectory()
|
||||||
|
entry.delete(0, tk.END)
|
||||||
|
entry.insert(0, directory_path)
|
||||||
|
|
||||||
|
|
||||||
|
# первое поле с списком
|
||||||
|
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_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))
|
||||||
|
|
||||||
|
# кнопка
|
||||||
|
empty_button = tk.Button(root, text="->", command=Pars, font=font)
|
||||||
|
empty_button.pack(side="right", padx=10, pady=10)
|
||||||
|
|
||||||
|
root.mainloop()
|
||||||
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "kadastr14"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"lxml>=6.0.0",
|
||||||
|
]
|
||||||
505
seekers.py
Normal file
505
seekers.py
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from lxml import etree
|
||||||
|
import re
|
||||||
|
|
||||||
|
class CadastralRecord:
|
||||||
|
"""Класс для хранения информации о кадастровом номере и связанных с ним данных."""
|
||||||
|
|
||||||
|
def __init__(self, cadastral_number, source_xml_info, temp_file_path=None):
|
||||||
|
self.cadastral_number = cadastral_number # Основной кадастровый номер
|
||||||
|
self.source_xml_info = source_xml_info # Ссылка на XMLFileInfo где найден
|
||||||
|
self.temp_file_path = temp_file_path # Путь к файлу во временной папке
|
||||||
|
self.quarter_number = self._extract_quarter_number() # Номер квартала
|
||||||
|
self.related_numbers = [] # Смежные кадастровые номера из того же файла
|
||||||
|
|
||||||
|
def _extract_quarter_number(self):
|
||||||
|
"""Извлекает номер квартала из кадастрового номера."""
|
||||||
|
# Квартал - это первые три части номера (NN:NN:NNNNNNN)
|
||||||
|
parts = self.cadastral_number.split(':')
|
||||||
|
if len(parts) >= 3:
|
||||||
|
return ':'.join(parts[:3])
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_related_numbers(self, all_numbers_from_file):
|
||||||
|
"""Устанавливает список смежных кадастровых номеров из того же файла."""
|
||||||
|
# Убираем сам номер из списка смежных
|
||||||
|
self.related_numbers = [num for num in all_numbers_from_file if num != self.cadastral_number]
|
||||||
|
|
||||||
|
def get_object_type(self):
|
||||||
|
"""Определяет тип объекта по кадастровому номеру."""
|
||||||
|
parts = self.cadastral_number.split(':')
|
||||||
|
if len(parts) == 4:
|
||||||
|
# Полный кадастровый номер объекта недвижимости
|
||||||
|
return "Объект недвижимости"
|
||||||
|
elif len(parts) == 3:
|
||||||
|
# Номер квартала
|
||||||
|
return "Квартал"
|
||||||
|
else:
|
||||||
|
return "Неизвестный тип"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"CadastralRecord({self.cadastral_number}, квартал: {self.quarter_number})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
class XMLFileInfo:
|
||||||
|
"""Класс для хранения информации о XML файле и его происхождении."""
|
||||||
|
|
||||||
|
def __init__(self, original_name, content, source_path, archive_name=None):
|
||||||
|
self.original_name = original_name # Оригинальное имя файла
|
||||||
|
self.content = content # Содержимое файла (bytes)
|
||||||
|
self.source_path = source_path # Полный путь к источнику (файлу или архиву)
|
||||||
|
self.archive_name = archive_name # Имя архива, если файл из архива
|
||||||
|
self.is_from_archive = archive_name is not None
|
||||||
|
self.cadastral_numbers = [] # Список найденных кадастровых номеров
|
||||||
|
self.analysis_completed = False # Флаг выполненного анализа
|
||||||
|
|
||||||
|
def analyze_xml_content(self):
|
||||||
|
"""Анализирует содержимое XML файла и извлекает кадастровые номера."""
|
||||||
|
if self.analysis_completed:
|
||||||
|
return self.cadastral_numbers
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Парсим XML из bytes
|
||||||
|
root = etree.fromstring(self.content)
|
||||||
|
self.cadastral_numbers = self._extract_cadastral_numbers(root)
|
||||||
|
self.analysis_completed = True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при анализе XML файла {self.original_name}: {e}")
|
||||||
|
self.cadastral_numbers = []
|
||||||
|
self.analysis_completed = True
|
||||||
|
|
||||||
|
return self.cadastral_numbers
|
||||||
|
|
||||||
|
def _extract_cadastral_numbers(self, element):
|
||||||
|
"""Извлекает кадастровые номера из XML элемента."""
|
||||||
|
cadastral_numbers = set()
|
||||||
|
|
||||||
|
# Ищем кадастровые номера в атрибутах
|
||||||
|
if element.attrib:
|
||||||
|
for attr_name, attr_value in element.attrib.items():
|
||||||
|
if 'CadastralNumber' in attr_name or 'cadastralnumber' in attr_name.lower():
|
||||||
|
if self._is_valid_cadastral_number(attr_value):
|
||||||
|
cadastral_numbers.add(attr_value)
|
||||||
|
elif 'Definition' in attr_name:
|
||||||
|
if self._is_valid_cadastral_number(attr_value):
|
||||||
|
cadastral_numbers.add(attr_value)
|
||||||
|
|
||||||
|
# Ищем кадастровые номера в тексте элементов
|
||||||
|
if element.text and element.text.strip():
|
||||||
|
text = element.text.strip()
|
||||||
|
if self._is_valid_cadastral_number(text):
|
||||||
|
cadastral_numbers.add(text)
|
||||||
|
|
||||||
|
# Рекурсивно проходим по всем дочерним элементам
|
||||||
|
for child in element:
|
||||||
|
# Проверяем название тега на наличие ключевых слов
|
||||||
|
tag_lower = child.tag.lower()
|
||||||
|
if any(keyword in tag_lower for keyword in ['cad_number', 'cadastralnumber', 'quarter_cad_number', 'land_cad_number']):
|
||||||
|
if child.text and child.text.strip():
|
||||||
|
text = child.text.strip()
|
||||||
|
if self._is_valid_cadastral_number(text):
|
||||||
|
cadastral_numbers.add(text)
|
||||||
|
|
||||||
|
# Рекурсивно анализируем дочерние элементы
|
||||||
|
child_numbers = self._extract_cadastral_numbers(child)
|
||||||
|
cadastral_numbers.update(child_numbers)
|
||||||
|
|
||||||
|
return list(cadastral_numbers)
|
||||||
|
|
||||||
|
def _is_valid_cadastral_number(self, value):
|
||||||
|
"""Проверяет, является ли строка валидным кадастровым номером."""
|
||||||
|
if not value or not isinstance(value, str):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Паттерн для кадастрового номера: NN:NN:NNNNNNN:NNNN
|
||||||
|
# где N - цифра, количество цифр может варьироваться
|
||||||
|
pattern = r'^\d{1,2}:\d{1,2}:\d{6,7}:\d{1,4}$'
|
||||||
|
return bool(re.match(pattern, value.strip()))
|
||||||
|
|
||||||
|
def get_cadastral_numbers(self):
|
||||||
|
"""Возвращает список кадастровых номеров (с анализом если не выполнен)."""
|
||||||
|
if not self.analysis_completed:
|
||||||
|
self.analyze_xml_content()
|
||||||
|
return self.cadastral_numbers
|
||||||
|
|
||||||
|
def create_cadastral_records(self, temp_file_path=None):
|
||||||
|
"""Создает объекты CadastralRecord для каждого найденного кадастрового номера."""
|
||||||
|
cadastral_numbers = self.get_cadastral_numbers()
|
||||||
|
records = []
|
||||||
|
|
||||||
|
for number in cadastral_numbers:
|
||||||
|
record = CadastralRecord(number, self, temp_file_path)
|
||||||
|
record.set_related_numbers(cadastral_numbers)
|
||||||
|
records.append(record)
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
def get_display_name(self):
|
||||||
|
"""Возвращает удобное для отображения имя файла."""
|
||||||
|
if self.is_from_archive:
|
||||||
|
return f"{self.original_name} (из архива {self.archive_name})"
|
||||||
|
else:
|
||||||
|
return self.original_name
|
||||||
|
|
||||||
|
def get_safe_filename(self):
|
||||||
|
"""Возвращает безопасное имя файла для сохранения."""
|
||||||
|
if self.is_from_archive:
|
||||||
|
archive_stem = Path(self.archive_name).stem
|
||||||
|
return f"ARCHIVE_{archive_stem}_{Path(self.original_name).stem}.xml"
|
||||||
|
else:
|
||||||
|
return self.original_name
|
||||||
|
|
||||||
|
def save_to_temp(self, temp_dir):
|
||||||
|
"""Сохраняет файл во временную папку."""
|
||||||
|
safe_name = self.get_safe_filename()
|
||||||
|
dest_path = temp_dir / safe_name
|
||||||
|
|
||||||
|
# Если файл уже существует, добавляем номер
|
||||||
|
counter = 1
|
||||||
|
while dest_path.exists():
|
||||||
|
name_parts = dest_path.stem, counter, dest_path.suffix
|
||||||
|
dest_path = temp_dir / f"{name_parts[0]}_{name_parts[1]}{name_parts[2]}"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
dest_path.write_bytes(self.content)
|
||||||
|
return dest_path
|
||||||
|
|
||||||
|
class FileSeeker:
|
||||||
|
def __init__(self, search_dir):
|
||||||
|
self.search_dir = Path(search_dir)
|
||||||
|
# Создаем временную папку рядом с программой
|
||||||
|
program_dir = Path(__file__).parent
|
||||||
|
self.temp_dir = program_dir / "kadastr14_temp"
|
||||||
|
self.temp_dir.mkdir(exist_ok=True)
|
||||||
|
print(f"Временная папка создана: {self.temp_dir}")
|
||||||
|
|
||||||
|
def find_xml_files(self):
|
||||||
|
"""Ищет все XML-файлы рекурсивно в директории поиска и возвращает объекты XMLFileInfo."""
|
||||||
|
xml_files = list(self.search_dir.rglob('*.xml'))
|
||||||
|
xml_file_objects = []
|
||||||
|
|
||||||
|
print(f"Найдено XML файлов: {len(xml_files)}")
|
||||||
|
for file_path in xml_files:
|
||||||
|
print(f" - {file_path}")
|
||||||
|
try:
|
||||||
|
content = file_path.read_bytes()
|
||||||
|
xml_info = XMLFileInfo(
|
||||||
|
original_name=file_path.name,
|
||||||
|
content=content,
|
||||||
|
source_path=str(file_path)
|
||||||
|
)
|
||||||
|
xml_file_objects.append(xml_info)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Ошибка при чтении {file_path}: {e}")
|
||||||
|
|
||||||
|
return xml_file_objects
|
||||||
|
|
||||||
|
def find_archives(self):
|
||||||
|
"""Ищет все архивы (ZIP) рекурсивно в директории поиска."""
|
||||||
|
zip_files = list(self.search_dir.rglob('*.zip'))
|
||||||
|
print(f"Найдено ZIP архивов: {len(zip_files)}")
|
||||||
|
for file in zip_files:
|
||||||
|
print(f" - {file}")
|
||||||
|
return zip_files
|
||||||
|
|
||||||
|
def extract_xml_from_archive(self, archive_path):
|
||||||
|
"""Извлекает XML файлы из архива и возвращает объекты XMLFileInfo."""
|
||||||
|
xml_file_objects = []
|
||||||
|
archive_name = archive_path.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(archive_path, 'r') as zip_ref:
|
||||||
|
# Получаем список файлов в архиве
|
||||||
|
file_list = zip_ref.namelist()
|
||||||
|
xml_files_in_archive = [f for f in file_list if f.lower().endswith('.xml')]
|
||||||
|
|
||||||
|
print(f" В архиве {archive_name} найдено XML файлов: {len(xml_files_in_archive)}")
|
||||||
|
|
||||||
|
for xml_file in xml_files_in_archive:
|
||||||
|
try:
|
||||||
|
# Читаем содержимое файла из архива
|
||||||
|
content = zip_ref.read(xml_file)
|
||||||
|
xml_info = XMLFileInfo(
|
||||||
|
original_name=Path(xml_file).name,
|
||||||
|
content=content,
|
||||||
|
source_path=str(archive_path),
|
||||||
|
archive_name=archive_name
|
||||||
|
)
|
||||||
|
xml_file_objects.append(xml_info)
|
||||||
|
print(f" Найден в архиве: {xml_file}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Ошибка при извлечении {xml_file}: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Ошибка при обработке архива {archive_path}: {e}")
|
||||||
|
|
||||||
|
return xml_file_objects
|
||||||
|
|
||||||
|
def copy_to_temp(self):
|
||||||
|
"""Собирает все XML файлы (обычные и из архивов) и сохраняет их во временную папку."""
|
||||||
|
all_xml_objects = []
|
||||||
|
|
||||||
|
# Сначала собираем обычные XML файлы
|
||||||
|
print(f"\nПоиск обычных XML файлов...")
|
||||||
|
xml_objects = self.find_xml_files()
|
||||||
|
all_xml_objects.extend(xml_objects)
|
||||||
|
|
||||||
|
# Теперь обрабатываем архивы
|
||||||
|
print(f"\nПоиск и обработка архивов...")
|
||||||
|
archives = self.find_archives()
|
||||||
|
|
||||||
|
for archive_path in archives:
|
||||||
|
print(f"\nОбработка архива: {archive_path.name}")
|
||||||
|
archive_xml_objects = self.extract_xml_from_archive(archive_path)
|
||||||
|
all_xml_objects.extend(archive_xml_objects)
|
||||||
|
|
||||||
|
# Сохраняем все найденные XML файлы во временную папку
|
||||||
|
print(f"\nСохранение XML файлов в: {self.temp_dir}")
|
||||||
|
saved_files = []
|
||||||
|
|
||||||
|
for xml_obj in all_xml_objects:
|
||||||
|
try:
|
||||||
|
saved_path = xml_obj.save_to_temp(self.temp_dir)
|
||||||
|
saved_files.append(saved_path)
|
||||||
|
print(f" Сохранен: {xml_obj.get_display_name()} -> {saved_path.name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Ошибка при сохранении {xml_obj.get_display_name()}: {e}")
|
||||||
|
|
||||||
|
print(f"\nВсего найдено XML файлов: {len(all_xml_objects)}")
|
||||||
|
print(f"Успешно сохранено: {len(saved_files)}")
|
||||||
|
|
||||||
|
return all_xml_objects, saved_files
|
||||||
|
|
||||||
|
def collect_all_cadastral_records(self, xml_objects, saved_files):
|
||||||
|
"""Собирает все кадастровые записи из всех XML файлов."""
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"СБОР КАДАСТРОВЫХ ЗАПИСЕЙ")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
|
||||||
|
all_records = []
|
||||||
|
file_path_mapping = {}
|
||||||
|
|
||||||
|
# Создаем маппинг XML объектов к путям сохраненных файлов
|
||||||
|
for i, xml_obj in enumerate(xml_objects):
|
||||||
|
if i < len(saved_files):
|
||||||
|
file_path_mapping[xml_obj] = saved_files[i]
|
||||||
|
|
||||||
|
# Собираем записи из каждого файла
|
||||||
|
for xml_obj in xml_objects:
|
||||||
|
temp_file_path = file_path_mapping.get(xml_obj)
|
||||||
|
records = xml_obj.create_cadastral_records(temp_file_path)
|
||||||
|
all_records.extend(records)
|
||||||
|
|
||||||
|
if records:
|
||||||
|
print(f"📄 {xml_obj.get_display_name()}: {len(records)} кадастровых записей")
|
||||||
|
|
||||||
|
print(f"\nВсего собрано кадастровых записей: {len(all_records)}")
|
||||||
|
return all_records
|
||||||
|
|
||||||
|
def print_cadastral_records_summary(self, records):
|
||||||
|
"""Выводит сводку по всем кадастровым записям."""
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"СВОДКА ПО КАДАСТРОВЫМ ЗАПИСЯМ")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
|
||||||
|
if not records:
|
||||||
|
print("Кадастровые записи не найдены.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Группируем по типам объектов
|
||||||
|
by_type = {}
|
||||||
|
by_quarter = {}
|
||||||
|
unique_numbers = set()
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
obj_type = record.get_object_type()
|
||||||
|
if obj_type not in by_type:
|
||||||
|
by_type[obj_type] = []
|
||||||
|
by_type[obj_type].append(record)
|
||||||
|
|
||||||
|
if record.quarter_number:
|
||||||
|
if record.quarter_number not in by_quarter:
|
||||||
|
by_quarter[record.quarter_number] = []
|
||||||
|
by_quarter[record.quarter_number].append(record)
|
||||||
|
|
||||||
|
unique_numbers.add(record.cadastral_number)
|
||||||
|
|
||||||
|
print(f"📊 Общая статистика:")
|
||||||
|
print(f" Всего записей: {len(records)}")
|
||||||
|
print(f" Уникальных номеров: {len(unique_numbers)}")
|
||||||
|
print(f" Кварталов: {len(by_quarter)}")
|
||||||
|
|
||||||
|
print(f"\n📋 По типам объектов:")
|
||||||
|
for obj_type, type_records in by_type.items():
|
||||||
|
print(f" {obj_type}: {len(type_records)} записей")
|
||||||
|
|
||||||
|
print(f"\n🏘️ По кварталам (топ-10):")
|
||||||
|
sorted_quarters = sorted(by_quarter.items(), key=lambda x: len(x[1]), reverse=True)
|
||||||
|
for quarter, quarter_records in sorted_quarters[:10]:
|
||||||
|
print(f" {quarter}: {len(quarter_records)} объектов")
|
||||||
|
|
||||||
|
def print_detailed_cadastral_records(self, records, max_records=20):
|
||||||
|
"""Выводит детальную информацию о кадастровых записях."""
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"ДЕТАЛЬНАЯ ИНФОРМАЦИЯ О КАДАСТРОВЫХ ЗАПИСЯХ (первые {max_records})")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
|
||||||
|
for i, record in enumerate(records[:max_records], 1):
|
||||||
|
print(f"\n{i}. 🏠 {record.cadastral_number}")
|
||||||
|
print(f" 📍 Тип: {record.get_object_type()}")
|
||||||
|
if record.quarter_number:
|
||||||
|
print(f" 🏘️ Квартал: {record.quarter_number}")
|
||||||
|
|
||||||
|
print(f" 📁 Источник: {record.source_xml_info.get_display_name()}")
|
||||||
|
print(f" 📂 Оригинальный файл: {record.source_xml_info.source_path}")
|
||||||
|
|
||||||
|
if record.temp_file_path:
|
||||||
|
print(f" 💾 Временный файл: {record.temp_file_path}")
|
||||||
|
|
||||||
|
if record.related_numbers:
|
||||||
|
print(f" 🔗 Смежные номера ({len(record.related_numbers)}):")
|
||||||
|
for j, related in enumerate(record.related_numbers[:3], 1):
|
||||||
|
print(f" {j}. {related}")
|
||||||
|
if len(record.related_numbers) > 3:
|
||||||
|
print(f" ... и еще {len(record.related_numbers) - 3} номеров")
|
||||||
|
else:
|
||||||
|
print(f" 🔗 Смежные номера: нет")
|
||||||
|
|
||||||
|
if len(records) > max_records:
|
||||||
|
print(f"\n... и еще {len(records) - max_records} записей")
|
||||||
|
|
||||||
|
def print_all_objects_info(self, xml_objects):
|
||||||
|
"""Выводит подробную информацию о всех найденных XML объектах."""
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"ПОДРОБНАЯ ИНФОРМАЦИЯ О ВСЕХ НАЙДЕННЫХ XML ОБЪЕКТАХ")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
print(f"Всего объектов: {len(xml_objects)}")
|
||||||
|
|
||||||
|
if not xml_objects:
|
||||||
|
print("Объекты не найдены.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Статистика по источникам
|
||||||
|
from_files = sum(1 for obj in xml_objects if not obj.is_from_archive)
|
||||||
|
from_archives = sum(1 for obj in xml_objects if obj.is_from_archive)
|
||||||
|
|
||||||
|
print(f"\nСтатистика по источникам:")
|
||||||
|
print(f" - Из обычных файлов: {from_files}")
|
||||||
|
print(f" - Из архивов: {from_archives}")
|
||||||
|
|
||||||
|
# Группируем по архивам для удобства
|
||||||
|
archive_groups = {}
|
||||||
|
regular_files = []
|
||||||
|
|
||||||
|
for obj in xml_objects:
|
||||||
|
if obj.is_from_archive:
|
||||||
|
if obj.archive_name not in archive_groups:
|
||||||
|
archive_groups[obj.archive_name] = []
|
||||||
|
archive_groups[obj.archive_name].append(obj)
|
||||||
|
else:
|
||||||
|
regular_files.append(obj)
|
||||||
|
|
||||||
|
# Выводим обычные файлы
|
||||||
|
if regular_files:
|
||||||
|
print(f"\n{'-'*60}")
|
||||||
|
print(f"ОБЫЧНЫЕ XML ФАЙЛЫ ({len(regular_files)} файлов)")
|
||||||
|
print(f"{'-'*60}")
|
||||||
|
|
||||||
|
for i, obj in enumerate(regular_files, 1):
|
||||||
|
self._print_object_details(obj, i)
|
||||||
|
|
||||||
|
# Выводим файлы из архивов
|
||||||
|
if archive_groups:
|
||||||
|
print(f"\n{'-'*60}")
|
||||||
|
print(f"XML ФАЙЛЫ ИЗ АРХИВОВ ({len(archive_groups)} архивов, {from_archives} файлов)")
|
||||||
|
print(f"{'-'*60}")
|
||||||
|
|
||||||
|
counter = 1
|
||||||
|
for archive_name, objects in archive_groups.items():
|
||||||
|
print(f"\n📦 АРХИВ: {archive_name}")
|
||||||
|
print(f" Путь: {objects[0].source_path}")
|
||||||
|
print(f" XML файлов в архиве: {len(objects)}")
|
||||||
|
|
||||||
|
for obj in objects:
|
||||||
|
self._print_object_details(obj, counter, indent=" ")
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"ИТОГО: {len(xml_objects)} XML файлов обработано")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
|
||||||
|
def _print_object_details(self, obj, number, indent=""):
|
||||||
|
"""Выводит детальную информацию об одном XML объекте."""
|
||||||
|
print(f"\n{indent}{number}. {obj.get_display_name()}")
|
||||||
|
print(f"{indent} 📄 Исходное имя: {obj.original_name}")
|
||||||
|
print(f"{indent} 📁 Источник: {obj.source_path}")
|
||||||
|
print(f"{indent} 💾 Размер содержимого: {len(obj.content):,} байт")
|
||||||
|
print(f"{indent} 🗂️ Безопасное имя: {obj.get_safe_filename()}")
|
||||||
|
|
||||||
|
# Запускаем анализ кадастровых номеров если не выполнен
|
||||||
|
cadastral_numbers = obj.get_cadastral_numbers() # Это запустит анализ если нужно
|
||||||
|
cadastral_count = len(cadastral_numbers)
|
||||||
|
|
||||||
|
print(f"{indent} 🔍 Анализ завершен: найдено {cadastral_count} кадастровых номеров")
|
||||||
|
if cadastral_count > 0:
|
||||||
|
# Показываем первые 3 номера
|
||||||
|
for i, num in enumerate(cadastral_numbers[:3], 1):
|
||||||
|
print(f"{indent} {i}. {num}")
|
||||||
|
if cadastral_count > 3:
|
||||||
|
print(f"{indent} ... и еще {cadastral_count - 3} номеров")
|
||||||
|
else:
|
||||||
|
print(f"{indent} ❌ Кадастровые номера не найдены")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Пример использования - ищем во всей папке с примерами
|
||||||
|
search_path = r"C:\Users\USER\Downloads\14. выборка выписок из ЕГРН по списку КН"
|
||||||
|
print(f"Начинаем поиск XML файлов в: {search_path}")
|
||||||
|
print("Поиск будет выполняться рекурсивно во всех подпапках...")
|
||||||
|
|
||||||
|
seeker = FileSeeker(search_path)
|
||||||
|
xml_objects, saved_files = seeker.copy_to_temp()
|
||||||
|
|
||||||
|
print(f"\nГотово! Временная папка: {seeker.temp_dir}")
|
||||||
|
print(f"Найдено XML объектов: {len(xml_objects)}")
|
||||||
|
print(f"Сохранено файлов: {len(saved_files)}")
|
||||||
|
|
||||||
|
# Собираем все кадастровые записи
|
||||||
|
cadastral_records = seeker.collect_all_cadastral_records(xml_objects, saved_files)
|
||||||
|
|
||||||
|
# Выводим сводку
|
||||||
|
seeker.print_cadastral_records_summary(cadastral_records)
|
||||||
|
|
||||||
|
# Выводим детальную информацию
|
||||||
|
seeker.print_detailed_cadastral_records(cadastral_records, max_records=10)
|
||||||
|
|
||||||
|
# Дополнительный анализ - находим записи с наибольшим количеством смежных номеров
|
||||||
|
if cadastral_records:
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"ЗАПИСИ С НАИБОЛЬШИМ КОЛИЧЕСТВОМ СМЕЖНЫХ НОМЕРОВ")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
|
||||||
|
# Сортируем по количеству смежных номеров
|
||||||
|
sorted_by_related = sorted(cadastral_records, key=lambda x: len(x.related_numbers), reverse=True)
|
||||||
|
|
||||||
|
for i, record in enumerate(sorted_by_related[:5], 1):
|
||||||
|
print(f"\n{i}. {record.cadastral_number}")
|
||||||
|
print(f" Смежных номеров: {len(record.related_numbers)}")
|
||||||
|
print(f" Источник: {record.source_xml_info.get_display_name()}")
|
||||||
|
if record.related_numbers:
|
||||||
|
print(f" Связанные номера: {', '.join(record.related_numbers[:5])}")
|
||||||
|
if len(record.related_numbers) > 5:
|
||||||
|
print(f" ... и еще {len(record.related_numbers) - 5} номеров")
|
||||||
|
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"АНАЛИЗ ЗАВЕРШЕН")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
print(f"📊 Всего кадастровых записей: {len(cadastral_records)}")
|
||||||
|
print(f"📁 Временная папка: {seeker.temp_dir}")
|
||||||
|
print(f"💾 Сохранено XML файлов: {len(saved_files)}")
|
||||||
0
unlocker.py
Normal file
0
unlocker.py
Normal file
54
uv.lock
generated
Normal file
54
uv.lock
generated
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 2
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kadastr14"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "lxml" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "lxml", specifier = ">=6.0.0" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lxml"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/b6/3a7971aa05b7be7dfebc7ab57262ec527775c2c3c5b2f43675cac0458cad/lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", size = 5657016, upload-time = "2025-07-03T19:19:06.008Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/31/c0267d03b16954a85ed6b065116b621d37f559553d9339c7dcc4943a76f1/lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", size = 5678523, upload-time = "2025-07-03T19:19:09.837Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user