File size: 6,212 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
"""
Модуль с абстрактным классом парсера.
"""

import os
from abc import ABC, abstractmethod
from typing import BinaryIO

from ..data_classes import ParsedDocument
from .file_types import FileType


class AbstractParser(ABC):
    """
    Абстрактный класс парсера документов.
    
    Все конкретные парсеры должны наследоваться от этого класса
    и реализовывать метод parse.
    """
    
    def __init__(self, file_types: FileType | list[FileType] | str | list[str] | None = None):
        """
        Инициализирует парсер.
        
        Args:
            file_types: Поддерживаемые типы файлов. Может быть одним из:
                - FileType - объект перечисления
                - list[FileType] - список объектов перечисления
                - str - строка с расширением файла (с точкой, например ".xml")
                - list[str] - список строк с расширениями
                - None - если не указан, парсер не ограничен типами
        """
        self.file_types = []
        
        if file_types is None:
            return
            
        # Преобразуем одиночный FileType в список
        if isinstance(file_types, FileType):
            self.file_types = [file_types]
        # Преобразуем список FileType в список
        elif isinstance(file_types, list) and all(isinstance(ft, FileType) for ft in file_types):
            self.file_types = file_types
        # Преобразуем строку расширения в FileType
        elif isinstance(file_types, str):
            try:
                self.file_types = [FileType.from_extension(file_types)]
            except ValueError:
                # Если не удалось найти подходящий FileType, создаем пустой список
                self.file_types = []
        # Преобразуем список строк расширений в список FileType
        elif isinstance(file_types, list) and all(isinstance(ft, str) for ft in file_types):
            self.file_types = []
            for ext in file_types:
                try:
                    self.file_types.append(FileType.from_extension(ext))
                except ValueError:
                    pass
    
    def _supported_extension(self, ext: str) -> bool:
        """
        Проверяет, поддерживается ли расширение файла.
        
        Этот метод должен быть переопределен в наследниках
        для указания поддерживаемых расширений.
        
        Args:
            ext (str): Расширение файла с точкой (.pdf, .docx и т.д.).
            
        Returns:
            bool: True, если расширение поддерживается, иначе False.
        """
        if not self.file_types:
            try:
                FileType.from_extension(ext)
                return True
            except ValueError:
                return False
        
        ext = ext.lower()
        for file_type in self.file_types:
            for supported_ext in file_type.value:
                if ext == supported_ext.lower():
                    return True
        return False
    
    def supports_file(self, file: str | BinaryIO | FileType) -> bool:
        """
        Проверяет, может ли парсер обработать файл.
        
        Args:
            file: Может быть одним из:
                - str: Путь к файлу
                - BinaryIO: Объект файла
                - FileType: Конкретный тип файла
            
        Returns:
            bool: True, если парсер поддерживает файл, иначе False.
        """
        # Если передан FileType, проверяем его наличие в списке поддерживаемых
        if isinstance(file, FileType):
            return file in self.file_types
        
        # Если переданы пустые file_types и не строка, не можем определить тип
        if not self.file_types and not isinstance(file, str):
            return False
            
        # Если передан путь к файлу, проверяем расширение
        if isinstance(file, str):
            _, ext = os.path.splitext(file)
            return self._supported_extension(ext)
            
        # Если передан бинарный объект, считаем что подходит
        # (конкретный тип будет проверен при вызове parse)
        return True
    
    @abstractmethod
    def parse(self, file: BinaryIO, file_type: FileType | None = None) -> ParsedDocument:
        """
        Парсит документ из объекта файла и возвращает его структурное представление.
        
        Args:
            file (BinaryIO): Объект файла для парсинга.
            file_type (FileType | None): Тип файла, если известен.
            
        Returns:
            ParsedDocument: Структурное представление документа.
        """
        pass
    
    @abstractmethod
    def parse_by_path(self, file_path: str) -> ParsedDocument:
        """
        Парсит документ по пути к файлу и возвращает его структурное представление.
        
        Args:
            file_path (str): Путь к файлу для парсинга.
            
        Returns:
            ParsedDocument: Структурное представление документа.
        """
        pass