File size: 6,401 Bytes
57cf043
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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]