Spaces:
Runtime error
Runtime error
| import logging | |
| from bs4 import BeautifulSoup | |
| from components.parser.abbreviations.abbreviation import Abbreviation | |
| from components.parser.xml.constants import (ABBREVIATIONS, | |
| ABBREVIATIONS_PATTERNS, | |
| REGULATIONS, REGULATIONS_PATTERNS) | |
| from components.parser.xml.structures import (ParsedRow, ParsedTable, | |
| ParsedTables) | |
| logger = logging.getLogger(__name__) | |
| class XMLTableParser: | |
| """ | |
| Класс для парсинга таблиц из xml файлов. | |
| """ | |
| def __init__(self, soup: BeautifulSoup): | |
| self.soup = soup | |
| self.abbreviations = [] | |
| def parse(self) -> ParsedTables: | |
| """ | |
| Парсинг таблиц из xml файла. | |
| Returns: | |
| ParsedTables - все таблицы, полученные из xml файла | |
| """ | |
| tables = self.soup.find_all('w:tbl') | |
| logger.info(f"Found {len(tables)} tables in XML") | |
| parsed_tables = [] | |
| self.abbreviations = [] | |
| for table_ind, table in enumerate(tables): | |
| table_name = self._extract_table_name(table) | |
| type_short = self._classify_special_types(table_name, table) | |
| first_row = table.find('w:tr') | |
| columns_count = len(first_row.find_all('w:tc')) if first_row else 0 | |
| parsed_table = self._parse_table( | |
| table=table, | |
| table_index=table_ind + 1, | |
| type_short=type_short, | |
| use_header=columns_count != 2, | |
| table_name=table_name, | |
| ) | |
| parsed_tables.append(parsed_table) | |
| # Если таблица содержит сокращения, извлекаем их | |
| if type_short == ABBREVIATIONS: | |
| abbreviations_from_table = self._extract_abbreviations_from_table( | |
| parsed_table | |
| ) | |
| if abbreviations_from_table: | |
| self.abbreviations.extend(abbreviations_from_table) | |
| logger.debug(f"Parsed {len(parsed_tables)} tables") | |
| # Создаем и нормализуем таблицы | |
| parsed_tables_obj = ParsedTables(tables=parsed_tables) | |
| normalized_tables = parsed_tables_obj.normalize() | |
| logger.debug(f"Normalized tables: {len(normalized_tables.tables)} main tables") | |
| if self.abbreviations: | |
| logger.debug( | |
| f"Extracted {len(self.abbreviations)} abbreviations from tables" | |
| ) | |
| return normalized_tables | |
| def get_abbreviations(self) -> list[Abbreviation]: | |
| """ | |
| Возвращает список аббревиатур, извлеченных из таблиц. | |
| Returns: | |
| list[Abbreviation]: Список аббревиатур | |
| """ | |
| return self.abbreviations | |
| def _extract_abbreviations_from_table( | |
| self, table: ParsedTable | |
| ) -> list[Abbreviation]: | |
| """ | |
| Извлечение аббревиатур из таблицы, помеченной как "сокращения". | |
| Args: | |
| table: ParsedTable - таблица сокращений | |
| Returns: | |
| list[Abbreviation]: Список аббревиатур | |
| """ | |
| abbreviations = [] | |
| # Проверяем, что таблица имеет нужный формат (обычно 2 колонки) | |
| for row in table.rows: | |
| if len(row.cols) >= 2: | |
| # Первая колонка обычно содержит сокращение, вторая - расшифровку | |
| short_form = row.cols[0].strip() | |
| full_form = row.cols[1].strip() | |
| # Создаем объект аббревиатуры только если оба поля не пусты | |
| if short_form and full_form: | |
| abbreviation = Abbreviation( | |
| short_form=short_form, | |
| full_form=full_form, | |
| ) | |
| # Обрабатываем аббревиатуру для определения типа и очистки | |
| abbreviation.process() | |
| abbreviations.append(abbreviation) | |
| return abbreviations | |
| def _parse_table( | |
| cls, | |
| table: BeautifulSoup, | |
| table_index: int, | |
| type_short: str | None, | |
| use_header: bool = False, | |
| table_name: str | None = None, | |
| ) -> ParsedTable: | |
| """ | |
| Парсинг таблицы. | |
| Args: | |
| table: BeautifulSoup - объект таблицы | |
| table_index: int - номер таблицы в xml-файле | |
| type_short: str | None - например, "сокращения" или "регламентирующие документы" | |
| use_header: bool - рассматривать ли первую строку таблицы как шапку таблицы | |
| table_name: str | None - название таблицы, если найдено | |
| Returns: | |
| ParsedTable - таблица, полученная из xml файла | |
| """ | |
| parsed_rows = [] | |
| header = [] if use_header else None | |
| rows = table.find_all('w:tr') | |
| for row_index, row in enumerate(rows): | |
| columns = row.find_all('w:tc') | |
| columns = [col.get_text() for col in columns] | |
| if (row_index == 0) and use_header: | |
| header = columns | |
| else: | |
| parsed_rows.append(ParsedRow(index=row_index, cols=columns)) | |
| # Вычисляем статистические показатели таблицы | |
| rows_count = len(parsed_rows) | |
| # Определяем модальное количество столбцов | |
| if rows_count > 0: | |
| col_counts = [len(row.cols) for row in parsed_rows] | |
| from collections import Counter | |
| modal_cols_count = Counter(col_counts).most_common(1)[0][0] | |
| else: | |
| modal_cols_count = len(header) if header else 0 | |
| # Инициализируем has_merged_cells как False, | |
| # actual value will be determined in normalize method | |
| has_merged_cells = False | |
| return ParsedTable( | |
| index=table_index, | |
| short_type=type_short, | |
| header=header, | |
| rows=parsed_rows, | |
| name=table_name, | |
| rows_count=rows_count, | |
| modal_cols_count=modal_cols_count, | |
| has_merged_cells=has_merged_cells, | |
| ) | |
| def _extract_columns_from_row( | |
| table_row: BeautifulSoup, | |
| ) -> list[str]: | |
| """ | |
| Парсинг колонок из строки таблицы. | |
| Args: | |
| table_row: BeautifulSoup - объект строки таблицы | |
| Returns: | |
| list[str] - список колонок, полученных из строки таблицы | |
| """ | |
| parsed_columns = [] | |
| for cell in table_row.find_all('w:tc'): | |
| cell_text_parts = [] | |
| for text_element in cell.find_all('w:t'): | |
| text_content = text_element.get_text() | |
| # Join all text parts from this cell and add to columns | |
| if cell_text_parts: | |
| parsed_columns.append(''.join(cell_text_parts)) | |
| return parsed_columns | |
| def _classify_special_types( | |
| table_name: str | None, | |
| table: BeautifulSoup, | |
| ) -> str | None: | |
| """ | |
| Поиск указаний на то, что таблица является специальной: "сокращения" или "регламентирующие документы". | |
| Args: | |
| table_name: str - название таблицы | |
| table: BeautifulSoup - объект таблицы | |
| Returns: | |
| str | None - либо "сокращения", либо "регламентирующие документы", либо None, если сокращения и регламенты не найдены | |
| """ | |
| first_row = table.find('w:tr').text | |
| # Проверяем наличие шаблонов в тексте перед таблицей | |
| for pattern in ABBREVIATIONS_PATTERNS: | |
| if (table_name and pattern.lower() in table_name.lower()) or ( | |
| pattern in first_row.lower() | |
| ): | |
| return ABBREVIATIONS | |
| for pattern in REGULATIONS_PATTERNS: | |
| if (table_name and pattern.lower() in table_name.lower()) or ( | |
| pattern in first_row.lower() | |
| ): | |
| return REGULATIONS | |
| return None | |
| def _extract_table_name( | |
| table: BeautifulSoup, | |
| ) -> str | None: | |
| """ | |
| Извлечение названия таблицы из текста перед таблицей. | |
| Метод ищет строки, содержащие типичные маркеры заголовков таблиц, такие как | |
| "Таблица", "Таблица N", "Табл.", и т.д., с учетом различных вариантов написания. | |
| Args: | |
| before_table_xml: str - блок xml-файла, предшествующий таблице | |
| Returns: | |
| str | None - название таблицы, если найдено, иначе None | |
| """ | |
| # Создаем объект BeautifulSoup для парсинга XML фрагмента | |
| previous_paragraph = table.find_previous('w:p') | |
| if previous_paragraph: | |
| return previous_paragraph.get_text() | |
| return None | |