fix: corrigir filtro de ativos, remover count de areas e navegacao ao clicar

This commit is contained in:
Frederico Castro
2025-12-20 12:22:45 -03:00
parent 45ab7412fe
commit 342e52880e
10 changed files with 2179 additions and 15 deletions

View File

@@ -0,0 +1,221 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import datetime as dt
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Iterable
from openpyxl import load_workbook
def _excel_col_letter(index_1_based: int) -> str:
result = ""
n = index_1_based
while n:
n, rem = divmod(n - 1, 26)
result = chr(ord("A") + rem) + result
return result
def _stringify_cell(value) -> str:
if value is None:
return ""
if isinstance(value, (dt.datetime, dt.date)):
return value.isoformat()
return str(value)
def _troff_escape(text: str) -> str:
text = text.replace("\\", "\\\\")
text = text.replace("\t", " ")
text = text.replace("|", "¦")
return text
def _troff_field(text: str) -> str:
text = _troff_escape(text)
if not text:
return "T{\n\\&\nT}"
lines = text.splitlines() or [text]
safe_lines: list[str] = []
for line in lines:
if line.startswith((".", "'")):
safe_lines.append(r"\&" + line)
else:
safe_lines.append(line)
body = "\n".join(safe_lines)
return f"T{{\n{body}\nT}}"
def _used_bounds(ws) -> tuple[int, int]:
last_row = 0
last_col = 0
for row_idx, row in enumerate(ws.iter_rows(values_only=True), start=1):
if any(v not in (None, "") for v in row):
last_row = row_idx
for col_idx, v in enumerate(row, start=1):
if v not in (None, ""):
last_col = max(last_col, col_idx)
return last_row, last_col
def _iter_rows(ws, max_row: int, max_col: int) -> Iterable[list[str]]:
for row in ws.iter_rows(min_row=1, max_row=max_row, min_col=1, max_col=max_col, values_only=True):
yield [_stringify_cell(v) for v in row]
def _emit_table(rows: list[list[str]], *, col_slice: slice, title: str) -> str:
cols = list(range(col_slice.start or 0, col_slice.stop or len(rows[0])))
ncols = len(cols)
if ncols <= 0:
return ""
spec = " ".join(["l"] * ncols) + "."
out: list[str] = []
out.append(".LP")
out.append(rf"\fB{_troff_escape(title)}\fP")
out.append(".TS")
out.append("tab(|) expand;")
out.append(spec)
header = rows[0]
header_fields = []
for i in cols:
header_fields.append(_troff_field(header[i]))
out.append("|".join(header_fields))
for row in rows[1:]:
fields = [_troff_field(row[i]) for i in cols]
out.append("|".join(fields))
out.append(".TE")
out.append(".LP")
return "\n".join(out) + "\n"
def build_troff_from_xlsx(xlsx_path: Path, generated_at: dt.datetime) -> str:
wb = load_workbook(xlsx_path, data_only=False)
lines: list[str] = []
lines.append('.\" Auto-generated from XLSX')
lines.append(".po 1i")
lines.append(".ll 6.5i")
lines.append(".ps 10")
lines.append(".vs 12")
lines.append("")
lines.append(".ps 18")
lines.append(".vs 22")
lines.append(".ce")
lines.append(r"\fBDefinição de Ranking — AtuaCAPES\fP")
lines.append(".sp 0.5")
lines.append(".ps 11")
lines.append(".vs 14")
lines.append(".ce")
lines.append("Documento gerado automaticamente")
lines.append(".sp 0.5")
lines.append(".ce")
lines.append(_troff_escape(generated_at.strftime("%Y-%m-%d %H:%M")))
lines.append(".sp 1")
lines.append(rf"Fonte: \fB{_troff_escape(str(xlsx_path))}\fP")
lines.append(".LP")
lines.append("Conteúdo: todas as abas da planilha, com critérios e regras conforme definidos no arquivo de entrada.")
lines.append(".bp")
lines.append(".ps 14")
lines.append(".vs 18")
lines.append(r"\fBSumário de Abas\fP")
lines.append(".ps 10")
lines.append(".vs 12")
lines.append(".sp 0.5")
for i, name in enumerate(wb.sheetnames, start=1):
lines.append(rf"{i}. \fB{_troff_escape(name)}\fP")
lines.append(".br")
lines.append(".bp")
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
max_row, max_col = _used_bounds(ws)
if max_row == 0 or max_col == 0:
continue
rows = list(_iter_rows(ws, max_row=max_row, max_col=max_col))
if not rows:
continue
lines.append(".ps 14")
lines.append(".vs 18")
lines.append(rf"\fB{_troff_escape(sheet_name)}\fP")
lines.append(".ps 10")
lines.append(".vs 12")
lines.append(".LP")
lines.append(rf"Linhas: \fB{max_row}\fP — Colunas: \fB{max_col}\fP")
max_cols_per_table = 8
if max_col <= max_cols_per_table:
start_letter = _excel_col_letter(1)
end_letter = _excel_col_letter(max_col)
lines.append(_emit_table(rows, col_slice=slice(0, max_col), title=f"Colunas {start_letter}{end_letter}"))
else:
start = 0
while start < max_col:
end = min(start + max_cols_per_table, max_col)
start_letter = _excel_col_letter(start + 1)
end_letter = _excel_col_letter(end)
lines.append(
_emit_table(
rows,
col_slice=slice(start, end),
title=f"Colunas {start_letter}{end_letter}",
)
)
start = end
lines.append(".bp")
return "\n".join(lines)
def render_pdf_from_ms(ms_text: str, pdf_path: Path, *, workdir: Path) -> None:
pdf_path.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=workdir) as td:
td_path = Path(td)
ms_path = td_path / "doc.ms"
ps_path = td_path / "doc.ps"
ms_path.write_text(ms_text, encoding="utf-8")
groff_cmd = ["groff", "-Kutf8", "-Tps", "-t", str(ms_path)]
ps_bytes = subprocess.check_output(groff_cmd)
ps_path.write_bytes(ps_bytes)
subprocess.check_call(["ps2pdf", str(ps_path), str(pdf_path)])
def main() -> int:
parser = argparse.ArgumentParser(description="Gera PDF (profissional) a partir de planilha XLSX.")
parser.add_argument("xlsx", type=Path, help="Caminho do XLSX de entrada")
parser.add_argument(
"-o",
"--out",
type=Path,
default=Path("out/definicao_ranking_atuacapes.pdf"),
help="Caminho do PDF de saída (default: out/definicao_ranking_atuacapes.pdf)",
)
args = parser.parse_args()
xlsx_path: Path = args.xlsx
out_pdf: Path = args.out
generated_at = dt.datetime.now().astimezone()
ms_text = build_troff_from_xlsx(xlsx_path=xlsx_path, generated_at=generated_at)
render_pdf_from_ms(ms_text=ms_text, pdf_path=out_pdf, workdir=Path(os.getcwd()))
print(out_pdf)
return 0
if __name__ == "__main__":
raise SystemExit(main())