"""Минимальный сборщик xlsx в памяти для тестов (без openpyxl).
Поддерживает: shared strings (включая красные ранки), цифры, мержи,
скрытые столбцы. Формат достаточно строгий, чтобы покрывать реальный
парсер (он читает именно этот поднабор OOXML).
"""
from __future__ import annotations
import io
import zipfile
from dataclasses import dataclass, field
from xml.sax.saxutils import escape
def col_letter(idx: int) -> str:
"""0-based индекс столбца -> 'A', 'B', ..., 'AA'."""
s = ""
idx += 1
while idx:
idx, rem = divmod(idx - 1, 26)
s = chr(ord("A") + rem) + s
return s
@dataclass
class SheetSpec:
rows: list[list]
merges: list[str] = field(default_factory=list)
hidden_cols: list[tuple[int, int]] = field(default_factory=list) # 0-based, инклязивно
hidden_rows: list[int] = field(default_factory=list) # 0-based
def build_workbook(sheets: dict[str, SheetSpec]) -> bytes:
shared: list[str] = []
shared_index: dict[str, int] = {}
red_runs: dict[int, list[tuple[str, bool]]] = {}
def intern(value) -> tuple[str, str, object]:
"""-> (тип ячейки, атрибут t, значение v / None)"""
if value is None or value == "":
return None, "", None
if isinstance(value, (int, float)):
return "n", "", str(value)
if isinstance(value, list):
idx = len(shared)
shared.append(None)
red_runs[idx] = value
return "s", "s", idx
idx = shared_index.get(value)
if idx is None:
idx = len(shared)
shared_index[value] = idx
shared.append(value)
return "s", "s", idx
sheet_xml: dict[str, str] = {}
rels = []
sheet_defs = []
for i, (name, spec) in enumerate(sheets.items(), start=1):
rel_id = f"rId{i}"
rels.append(
f'