# -*- coding: utf-8 -*- """Читатель .ods (odfpy). Разворачивает повторяющиеся ячейки/строки.""" from __future__ import annotations import datetime from typing import List from odf.opendocument import load from odf.table import Table, TableCell, TableRow from odf.teletype import extractText from odf.text import P from ..models import CellValue from .base import SourceReader # Пределы на «бесконечные» повторения пустого хвоста таблицы. _MAX_COL_REPEAT = 200 _MAX_ROW_REPEAT = 200 class OdsReader(SourceReader): def _rows(self) -> List[List[CellValue]]: doc = load(str(self.path)) table = doc.spreadsheet.getElementsByType(Table)[0] rows: List[List[CellValue]] = [] for tr in table.getElementsByType(TableRow): row = self._row_values(tr) row_rep = _repeat(tr, "numberrowsrepeated", _MAX_ROW_REPEAT) for _ in range(row_rep): rows.append(list(row)) _trim_trailing_empty(rows) return rows @staticmethod def _row_values(tr: TableRow) -> List[CellValue]: values: List[CellValue] = [] for cell in tr.getElementsByType(TableCell): col_rep = _repeat(cell, "numbercolumnsrepeated", _MAX_COL_REPEAT) value = _cell_value(cell) values.extend([value] * col_rep) return values def _repeat(element, attr: str, limit: int) -> int: raw = element.getAttribute(attr) n = int(raw) if raw else 1 # огромные повторения — это пустой хвост листа, не размножаем return n if n <= limit else 1 def _cell_value(cell: TableCell) -> CellValue: vtype = cell.getAttribute("valuetype") if vtype in ("float", "currency", "percentage"): raw = cell.getAttribute("value") if raw is not None: num = float(raw) return int(num) if num.is_integer() else num if vtype == "date": raw = cell.getAttribute("datevalue") if raw: return _parse_date(raw) text = _cell_text(cell).strip() return text or None def _cell_text(cell: TableCell) -> str: # extractText корректно собирает текст из , (кратные # пробелы), и — ручной обход childNodes их терял. return "\n".join(extractText(p) for p in cell.getElementsByType(P)) def _parse_date(raw: str): for fmt in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d"): try: dt = datetime.datetime.strptime(raw, fmt) if dt.hour == dt.minute == dt.second == 0: return dt.date() return dt except ValueError: continue return raw def _trim_trailing_empty(rows: List[List[CellValue]]) -> None: while rows and all(v in (None, "") for v in rows[-1]): rows.pop()