Files
VSTU_Schedule_Parser/translations.py
2025-09-11 15:42:41 +03:00

230 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# --- Абстрактный базовый класс (Контракт) ---
from abc import ABC, abstractmethod
import openpyxl
import xlrd
from coord import Coord, Merged
EMPTY_CTYPES = [xlrd.XL_CELL_EMPTY, xlrd.XL_CELL_BLANK]
class TranschendentnostCell:
def __init__(self, value, is_empty):
self.value = value
self._is_empty = is_empty
def is_empty(self):
self._is_empty
class ExcelSheetReader(ABC):
"""
Абстрактный базовый класс, определяющий интерфейс для чтения данных из листа Excel.
Использует 0-индексированную систему координат, как в xlrd.
"""
def __init__(self, file_path):
self.file_path = file_path
@abstractmethod
def get_cell_value(self, row, col):
"""Возвращает значение ячейки по 0-индексированным координатам."""
pass
@abstractmethod
def get_border_style(self, coord: Coord, side):
"""
Возвращает числовой стиль границы (как в xlrd).
side: 'left', 'right', 'top', 'bottom'
"""
pass
@abstractmethod
def get_merged_cells(self):
"""Возвращает список объединенных ячеек в формате xlrd: [(rlo, rhi, clo, chi), ...]."""
pass
@abstractmethod
def get_row_count(self):
"""Возвращает общее количество строк на листе."""
pass
@abstractmethod
def get_row_values(self, row_index):
"""Возвращает список значений всех ячеек в строке."""
pass
#@abstractmethod
def info(self):
"""Возвращает строку информации"""
return "TODO: info"
@abstractmethod
def cell(self, row, col):
"""Возвращает абстрактную клетку"""
pass
def find(self, query = None):
for rx in range(self.get_row_count()):
i = 0
for x in self.get_row_values(rx):
if x == query:
return Coord(rx, i)
i += 1
return None
def get_merged(self, rowx: int, colx: int):
"""Даём ей координаты ячейки таблицы а она выдаёт её границы если переданные координаты находятся 'внутри' объединённой ячейки"""
for crange in self.get_merged_cells():
rlo, rhi, clo, chi = crange
chi -= 1
rhi -= 1
if rlo <= rowx <= rhi and chi >= colx >= clo:
return rlo, clo, rhi, chi
# если ячейка не часть объединённых то начала и концы у неё равны.
return rowx, colx, rowx, colx
def get_merged_coord(self, coord: "Coord") -> Merged:
merged = self.get_merged(coord.row, coord.col)
return Merged(coord1=Coord(merged[0], merged[1]), coord2=Coord(merged[2], merged[3]))
# --- Реализация №1: Обертка для xlrd ---
class XlrdSheetReader(ExcelSheetReader):
def __init__(self, file_path, sheet_index=0):
super().__init__(file_path)
self.book = xlrd.open_workbook(file_path, formatting_info=True)
self.sheet = self.book.sheet_by_index(sheet_index)
def get_cell_value(self, row, col):
# Проверка на выход за пределы таблицы, чтобы избежать ошибок
if row < self.sheet.nrows and col < self.sheet.ncols:
return self.sheet.cell_value(row, col)
return None
def info(self):
print("The number of worksheets is {0}".format(self.book.nsheets))
print("Worksheet name(s): {0}".format(self.book.sheet_names()))
return "'{0}': size: {1}x{2} names: ".format(self.sheet.name, self.sheet.nrows, self.sheet.ncols, " ".join(self.book.sheet_names()))
def cell(self, row, col):
"""Возвращает абстрактную клетку"""
c = self.sheet.cell(row, col)
return TranschendentnostCell(c.value, c.ctype in EMPTY_CTYPES)
def get_border_style(self, coord: Coord, side):
row = coord.row
col = coord.col
if row >= self.sheet.nrows or col >= self.sheet.ncols:
return 0 # Нет границы за пределами листа
cell = self.sheet.cell(row, col)
xf = self.book.xf_list[cell.xf_index]
border = xf.border
style_map = {
'left': border.left_line_style,
'right': border.right_line_style,
'top': border.top_line_style,
'bottom': border.bottom_line_style,
}
return style_map.get(side, 0)
def get_merged_cells(self):
return self.sheet.merged_cells
def get_row_count(self):
return self.sheet.nrows
def get_row_values(self, row_index):
if row_index < self.sheet.nrows:
return self.sheet.row_values(row_index)
return []
# --- Реализация №2: Обертка-транслятор для openpyxl ---
class OpenpyxlSheetReader(ExcelSheetReader):
def __init__(self, file_path, sheet_name=None):
super().__init__(file_path)
self.workbook = openpyxl.load_workbook(file_path, data_only=True)
self.sheet = self.workbook[sheet_name] if sheet_name else self.workbook.active
# Словарь для трансляции стилей границ openpyxl в числовые коды xlrd
self.BORDER_STYLE_MAP = {
'thin': 1, 'medium': 2, 'dashed': 3, 'dotted': 4, 'thick': 5,
'double': 6, 'hair': 7, 'mediumDashed': 8, 'dashDot': 9,
'mediumDashDot': 10, 'dashDotDot': 11, 'mediumDashDotDot': 12,
'slantDashDot': 13
}
def _get_cell(self, row, col):
"""Внутренний метод для получения ячейки с преобразованием координат."""
# openpyxl использует 1-индексированную систему
if row < self.sheet.max_row and col < self.sheet.max_column:
return self.sheet.cell(row=row + 1, column=col + 1)
return None
def cell(self, row, col):
"""Возвращает абстрактную клетку"""
c = self._get_cell(row, col)
is_empty = (c.value is None)
return TranschendentnostCell("" if is_empty else c.value, is_empty)
def get_cell_value(self, row, col):
cell = self._get_cell(row, col)
return cell.value if cell else None
def get_border_style(self, coord: Coord, side):
cell = self._get_cell(coord.row, coord.col)
if not cell:
return 0
border_side = getattr(cell.border, side, None)
if border_side:
return self.BORDER_STYLE_MAP.get(border_side.style, 0)
return 0
def get_merged_cells(self):
# Преобразуем формат объединенных ячеек openpyxl в формат xlrd
merged_list = []
for merged_range in self.sheet.merged_cells.ranges:
# Преобразуем 1-индексированные границы в 0-индексированный формат xlrd (rlo, rhi, clo, chi)
# rhi и chi в xlrd являются эксклюзивными (до, но не включая)
rlo = merged_range.min_row - 1
rhi = merged_range.max_row
clo = merged_range.min_col - 1
chi = merged_range.max_col
merged_list.append((rlo, rhi, clo, chi))
return merged_list
def get_row_count(self):
return self.sheet.max_row
def get_row_values(self, row_index):
if row_index < self.sheet.max_row:
# Получаем значения из генератора
return [cell.value for cell in self.sheet[row_index + 1]]
return []
# --- Фабричная функция (Ваша единственная точка входа) ---
def create_reader(file_path, **kwargs) -> ExcelSheetReader:
"""
Создает и возвращает подходящий экземпляр ридера в зависимости от расширения файла.
"""
if file_path.lower().endswith('.xlsx'):
print("Используется движок openpyxl для .xlsx")
return OpenpyxlSheetReader(file_path, **kwargs)
elif file_path.lower().endswith('.xls'):
print("Используется движок xlrd для .xls")
return XlrdSheetReader(file_path, **kwargs)
else:
raise ValueError("Неподдерживаемый формат файла. Используйте .xls или .xlsx")