File size: 5,936 Bytes
86c402d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Модуль с универсальным парсером, объединяющим все специфичные парсеры.
"""

import logging
import os
from typing import BinaryIO

from ..data_classes import ParsedDocument
from .abstract_parser import AbstractParser
from .file_types import FileType
from .parser_factory import ParserFactory
from .specific_parsers import (
    DocParser,
    DocxParser,
    EmailParser,
    HTMLParser,
    MarkdownParser,
    PDFParser,
    XMLParser,
)

logger = logging.getLogger(__name__)


class UniversalParser:
    """
    Универсальный парсер, объединяющий все специфичные парсеры.

    Использует фабрику парсеров для выбора подходящего парсера
    на основе типа файла.
    """

    def __init__(self):
        """
        Инициализирует универсальный парсер и регистрирует все доступные парсеры.
        """
        self.factory = ParserFactory()

        # Регистрируем все доступные парсеры
        self.register_parsers(
            [
                XMLParser(),  # Реализованный парсер
                PDFParser(),  # Нереализованный парсер
                DocParser(),  # Нереализованный парсер
                DocxParser(),  # Реализованный парсер
                EmailParser(),  # Нереализованный парсер
                MarkdownParser(),  # Нереализованный парсер
                HTMLParser(),  # Нереализованный парсер
            ]
        )

    def register_parser(self, parser: AbstractParser) -> None:
        """
        Регистрирует парсер в фабрике.

        Args:
            parser (AbstractParser): Парсер для регистрации.
        """
        self.factory.register_parser(parser)

    def register_parsers(self, parsers: list[AbstractParser]) -> None:
        """
        Регистрирует несколько парсеров в фабрике.

        Args:
            parsers (list[AbstractParser]): Список парсеров для регистрации.
        """
        for parser in parsers:
            self.register_parser(parser)

    def parse_by_path(self, file_path: str) -> ParsedDocument | None:
        """
        Парсит документ по пути к файлу, используя подходящий парсер.

        Args:
            file_path (str): Путь к файлу для парсинга.

        Returns:
            ParsedDocument | None: Структурное представление документа или None,
                                 если подходящий парсер не найден.

        Raises:
            ValueError: Если файл не существует или не может быть прочитан.
        """
        if not os.path.exists(file_path):
            raise ValueError(f"Файл не найден: {file_path}")

        # Находим подходящий парсер
        parser = self.factory.get_parser(file_path)
        if not parser:
            logger.warning(f"Не найден подходящий парсер для файла: {file_path}")
            return None

        # Парсим документ
        try:
            return parser.parse_by_path(file_path)
        except Exception as e:
            logger.error(f"Ошибка при парсинге файла {file_path}: {e}")
            raise

    def parse(
        self, file: BinaryIO, file_type: FileType | str | None = None
    ) -> ParsedDocument | None:
        """
        Парсит документ из объекта файла, используя подходящий парсер.

        Args:
            file (BinaryIO): Объект файла для парсинга.
            file_type: Тип файла, может быть объектом FileType или строкой с расширением.
                Например: FileType.XML или ".xml"

        Returns:
            ParsedDocument | None: Структурное представление документа или None,
                                 если подходящий парсер не найден.

        Raises:
            ValueError: Если файл не может быть прочитан или распарсен.
        """
        # Преобразуем строковое расширение в FileType, если нужно
        ft = None
        if isinstance(file_type, str):
            try:
                ft = FileType.from_extension(file_type)
            except ValueError:
                logger.warning(f"Неизвестное расширение файла: {file_type}")
                return None
        else:
            ft = file_type

        if ft is None:
            logger.warning("Тип файла не указан при парсинге из объекта файла")
            return None

        # Получаем парсер для указанного типа файла
        parsers = [p for p in self.factory.parsers if p.supports_file(ft)]
        if not parsers:
            logger.warning(f"Не найден подходящий парсер для типа файла: {ft}")
            return None

        # Используем первый подходящий парсер
        parser = parsers[0]

        # Парсим документ
        try:
            return parser.parse(file, ft)
        except Exception as e:
            logger.error(f"Ошибка при парсинге файла: {e}")
            raise