116 lines
5.1 KiB
Python
116 lines
5.1 KiB
Python
"""
|
||
Модель для хранения информации об 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)}"
|