fix: corrigir filtro de ativos, remover count de areas e navegacao ao clicar
This commit is contained in:
221
tools/generate_ranking_pdf.py
Normal file
221
tools/generate_ranking_pdf.py
Normal 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())
|
||||
Reference in New Issue
Block a user