import logging import os from pathlib import Path from bs4 import BeautifulSoup from tqdm import tqdm from components.parser.docx_to_xml import DocxToXml from components.parser.xml.structures import ParsedXML, ParsedXMLs from components.parser.xml.xml_info_parser import XMLInfoParser from components.parser.xml.xml_table_parser import XMLTableParser from components.parser.xml.xml_text_parser import XMLTextParser logger = logging.getLogger(__name__) class XMLParser: """ Класс для парсинга xml файлов. """ @classmethod def parse_all( cls, filepath: os.PathLike, encoding: str = 'cp866', include_content: bool = False, ignore_files_contains: list[str] = [], use_tqdm: bool = False, ) -> ParsedXMLs: """ Парсинг всех xml файлов в директории. Args: filepath: os.PathLike - путь к директории с xml файлами encoding: str - кодировка файлов include_content: bool - включать ли содержимое файлов в результат ignore_files_contains: list[str] - игнорировать файлы, содержащие эти строки в названии use_tqdm: bool - использовать ли прогресс-бар Returns: ParsedXMLs - данные, полученные из всех xml файлов """ files = cls._get_recursive_files(filepath, ignore_files_contains) logger.info(f"Found {len(files)} files to parse") if use_tqdm: files = tqdm(files, desc='Парсинг файлов') parsed_xmls = [cls.parse(file, encoding, include_content) for file in files] logger.info(f"Parsed {len(parsed_xmls)} files") parsed_xmls = [ xml for xml in parsed_xmls if ( xml is not None and not any( ignore_file in xml.name for ignore_file in ignore_files_contains ) ) ] logger.info(f"Filtered {len(parsed_xmls)} files") return ParsedXMLs(parsed_xmls) @classmethod def parse( cls, filepath: os.PathLike, encoding: str = 'utf-8', include_content: bool = False, ) -> ParsedXML | None: """ Парсинг xml файла. Args: filepath: os.PathLike - путь к xml файлу encoding: str - кодировка файла include_content: bool - включать ли содержимое файла в результат Returns: ParsedXML - данные, полученные из xml файла """ if filepath.suffix in ['.docx', '.DOCX']: logger.info(f"Parsing docx file {filepath}") try: xml_text = DocxToXml(filepath).extract_document_xml() logger.info(f"Parsed docx file {filepath}") except Exception as e: logger.error(f"Error parsing docx file {filepath}: {e}") return None else: with open(filepath, 'r', encoding=encoding) as file: xml_text = file.read() soup = BeautifulSoup(xml_text, features='xml') # Создаем парсер информации и получаем базовые данные info_parser = XMLInfoParser(soup, filepath) parsed_xml = info_parser.parse() logger.debug(f"Parsed info for {filepath}") if not parsed_xml: logger.warning(f"Failed to parse info for {filepath}") return None if not include_content: logger.debug(f"Skipping content for {filepath}") return parsed_xml # Парсим таблицы и текст, сохраняя структурированные данные table_parser = XMLTableParser(soup) text_parser = XMLTextParser(soup) # Сохраняем структурированные данные вместо текста parsed_xml.tables = table_parser.parse() logger.debug(f"Parsed table content for {filepath}") parsed_xml.text = text_parser.parse() logger.debug(f"Parsed text content for {filepath}") # Собираем аббревиатуры из таблиц и текста abbreviations = [] # Получаем аббревиатуры из таблиц table_abbreviations = table_parser.get_abbreviations() if table_abbreviations: logger.debug(f"Got {len(table_abbreviations)} abbreviations from tables") abbreviations.extend(table_abbreviations) # Получаем аббревиатуры из текста text_abbreviations = text_parser.get_abbreviations() if text_abbreviations: logger.debug(f"Got {len(text_abbreviations)} abbreviations from text") abbreviations.extend(text_abbreviations) # Сохраняем все аббревиатуры в ParsedXML if abbreviations: logger.info(f"Total abbreviations extracted: {len(abbreviations)}") parsed_xml.abbreviations = abbreviations # Применяем аббревиатуры к содержимому документа parsed_xml.apply_document_abbreviations() logger.debug(f"Applied abbreviations to document content") return parsed_xml @classmethod def _get_recursive_files( cls, path_to_dir: os.PathLike, ignore_files_contains: list[str] = [], ) -> list[os.PathLike]: """ Получение всех xml файлов в директории любой вложенности. """ path_to_dir = Path(path_to_dir) relative_paths = [ path.relative_to(path_to_dir) for path in path_to_dir.glob('**/*.xml') if not any( ignore_file in path.name for ignore_file in ignore_files_contains ) ] return [Path(path_to_dir) / path for path in relative_paths]