"""Render ``.xlsx`` workbooks as HTML tables for the viewer (#xlsx). Read-only: formulas are shown as their text (``data_only=False``) so a round-trip through the viewer never depends on Excel's cached values. Write-side lives in ``backend.services.mutations.edit_xlsx_cells``. """ from __future__ import annotations import html from datetime import date, datetime from pathlib import Path from typing import Any from openpyxl import load_workbook from openpyxl.utils import get_column_letter # ponytail: hard caps bound the rendered grid (500 rows x 40 cols per sheet). # Raise them, or paginate per sheet, if a real workbook needs more. MAX_ROWS = 500 MAX_COLS = 40 def _fmt(value: Any) -> str: if value is None: return "" if isinstance(value, datetime): return value.strftime("%Y-%m-%d %H:%M") if isinstance(value, date): return value.isoformat() return str(value) def _trim(grid: list[list[str]]) -> list[list[str]]: """Drop trailing empty rows and columns (openpyxl pads to max_col).""" while grid and not any(grid[-1]): grid.pop() if not grid: return grid width = 0 for row in grid: for i in range(len(row) - 1, -1, -1): if row[i]: width = max(width, i + 1) break return [row[:width] for row in grid] def _table(grid: list[list[str]]) -> str: if not grid: return "

Feuille vide

" n_cols = max(len(row) for row in grid) out = [ ( '
' '' ) ] out += [f"" for c in range(1, n_cols + 1)] out.append("") for r, row in enumerate(grid, start=1): out.append(f'') for c, val in enumerate(row, start=1): ref = f"{get_column_letter(c)}{r}" out.append(f'') out.append("") out.append("
{get_column_letter(c)}
{r}{html.escape(val)}
") return "".join(out) def render_sheets(file_path: Path) -> list[dict[str, str]]: """Return ``[{"name": sheet_title, "html": table_html}, ...]``.""" wb = load_workbook(str(file_path), read_only=True, data_only=False) try: sheets = [] for ws in wb.worksheets: grid = [ [_fmt(v) for v in row] for row in ws.iter_rows( min_row=1, max_row=MAX_ROWS, max_col=MAX_COLS, values_only=True ) ] sheets.append({"name": ws.title, "html": _table(_trim(grid))}) return sheets finally: wb.close()