""" Модель для хранения информации об XML файле. """ import re from pathlib import Path from typing import List, Set, Optional, Tuple from lxml import etree class XMLFileInfo: def __init__(self, file_path: str): self.file_path = Path(file_path) self.main_cadastral_number: Optional[str] = None self.all_cadastral_numbers: Set[str] = set() def extract_cadastral_data(self) -> Tuple[Optional[str], Set[str]]: """ Извлекает основной номер и все номера из XML файла Возвращает (основной_номер, все_номера_в_файле) """ if not self.file_path.exists(): return None, set() try: tree = etree.parse(str(self.file_path)) root = tree.getroot() # Находим все кадастровые номера в файле all_numbers = self._find_all_cadastral_numbers(root) # Определяем основной номер (из объекта) main_number = self._find_main_cadastral_number(root, all_numbers) self.main_cadastral_number = main_number self.all_cadastral_numbers = all_numbers return main_number, all_numbers except Exception as e: print(f"Ошибка при обработке файла {self.file_path}: {e}") return None, set() def _find_all_cadastral_numbers(self, root) -> Set[str]: """Находит все валидные кадастровые номера в XML""" all_numbers = set() # Ищем по всем элементам for elem in root.iter(): if elem.text and self._is_valid_cadastral_number(elem.text): all_numbers.add(elem.text.strip()) return all_numbers def _find_main_cadastral_number(self, root, all_numbers: Set[str]) -> Optional[str]: """ Определяет основной кадастровый номер из объекта Приоритет: object > другие контейнеры > первый найденный """ # 1. Ищем в объектах - высший приоритет objects = root.xpath(".//object") for obj in objects: number = self._get_first_cadastral_from_element(obj) if number and number in all_numbers: return number # 2. Ищем в других контейнерах объектов недвижимости containers = [ 'land_record', 'building_record', 'construction_record', 'flat_record', 'extract_about_property_land', 'extract_about_property_building', 'extract_about_property_flat', 'extract_about_property_construction', 'property_object', 'realty_object' ] for container in containers: elements = root.xpath(f".//{container}") for elem in elements: number = self._get_first_cadastral_from_element(elem) if number and number in all_numbers: return number # 3. Если ничего не найдено в контейнерах, берем первый из всех найденных if all_numbers: return sorted(all_numbers)[0] return None def _get_first_cadastral_from_element(self, element) -> Optional[str]: """Находит первый кадастровый номер в элементе""" # Приоритетные теги для поиска priority_tags = ['cad_number', 'cadastral_number', 'land_cad_number', 'parcel_cad_number'] # Сначала ищем по приоритетным тегам for tag in priority_tags: elements = element.xpath(f".//{tag}") for elem in elements: if elem.text and self._is_valid_cadastral_number(elem.text): return elem.text.strip() # Если в приоритетных тегах не найдено, ищем по всем элементам for elem in element.iter(): if elem.text and self._is_valid_cadastral_number(elem.text): return elem.text.strip() return None def _is_valid_cadastral_number(self, text: str) -> bool: """Проверяет, является ли строка валидным кадастровым номером""" if not text: return False # Улучшенный паттерн для российских кадастровых номеров pattern = r'^\d{1,2}:\d{1,2}:\d{6,8}:\d{1,5}$' return bool(re.match(pattern, text.strip())) def __str__(self): return f"XML файл: {self.file_path.name}, основной номер: {self.main_cadastral_number}, всего номеров: {len(self.all_cadastral_numbers)}"