Files
scripts/doc-converter/scripts/converter.py
T

216 lines
8.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Autor: Descomplicar - Agência de Aceleração Digital
https://descomplicar.pt
Conversor de Markdown para DOCX e PDF - v9.0 FINAL
Resolve: páginas em branco, TOC, links preservados
"""
import os
import sys
import re
import shutil
from pathlib import Path
import subprocess
import argparse
import tempfile
# Importar configurações
from config import DOCX_OUTPUT_DIR, PDF_OUTPUT_DIR, PROCESSING_CONFIG
# Imports que precisam do venv
try:
import pypandoc
from docx import Document
from docx.shared import Inches, Pt
from docx.enum.text import WD_BREAK
from docx.oxml.shared import qn
except ImportError:
print("Erro: Dependências não instaladas. Execute o script de instalação ou ative o venv.", file=sys.stderr)
sys.exit(1)
def check_dependencies():
"""Verifica se as dependências de sistema estão instaladas."""
global libreoffice_available
libreoffice_available = True
if not shutil.which("pandoc"):
sys.exit("Erro: Pandoc não está instalado.")
if not shutil.which("libreoffice"):
print("Aviso: LibreOffice não encontrado. A conversão para PDF será ignorada.")
libreoffice_available = False
class DOCXConverter:
"""Conversor final com todos os problemas resolvidos."""
def __init__(self, input_path):
self.input_path = Path(input_path)
if not self.input_path.exists():
raise FileNotFoundError(f"Ficheiro de entrada não encontrado: {self.input_path}")
base_name = self.input_path.stem
self.output_docx_path = Path(DOCX_OUTPUT_DIR) / f"{base_name}.docx"
self.output_pdf_path = Path(PDF_OUTPUT_DIR) / f"{base_name}.pdf"
self.master_template_path = Path(__file__).resolve().parent / "template_mestre.docx"
def convert(self):
"""Orquestra o processo de conversão completo."""
print(f"A processar '{self.input_path.name}'...")
try:
self._generate_guide_from_template()
if libreoffice_available:
self._convert_to_pdf()
print(f"'{self.input_path.name}' convertido com sucesso para '{self.output_docx_path}'.")
except Exception as e:
print(f"ERRO FATAL ao processar '{self.input_path.name}': {e}", file=sys.stderr)
def _generate_guide_from_template(self):
"""Estratégia FINAL: Gerar documento completo com template + TOC + conteúdo + links."""
print(f"A combinar template com conteúdo para '{self.input_path.name}'...")
# 1. Ler e processar o markdown
try:
full_content = self.input_path.read_text(encoding='utf-8')
content_without_yaml = re.sub(r'^---\s*.*?\s*---\s*', '', full_content, flags=re.DOTALL).strip()
except Exception as e:
raise IOError(f"Erro ao ler o ficheiro Markdown: {e}")
# 2. Converter markdown para DOCX com TODOS os recursos
with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as temp_docx:
temp_docx_path = temp_docx.name
try:
# Configuração COMPLETA do Pandoc
pandoc_args = [
'--standalone',
'--from=markdown+raw_html+auto_identifiers+smart',
'--to=docx',
'--table-of-contents',
'--toc-depth=3',
'--variable=toc-title:Índice',
'--preserve-tabs',
'--wrap=none',
]
pypandoc.convert_text(
content_without_yaml,
'docx',
format='markdown',
outputfile=temp_docx_path,
extra_args=pandoc_args
)
print(f"✅ Conteúdo convertido para DOCX com TOC e links")
except Exception as e:
os.remove(temp_docx_path)
raise RuntimeError(f"Erro na conversão Pandoc: {e}")
# 3. Estratégia FINAL: Combinar preservando TUDO
try:
self._final_combination_strategy(temp_docx_path)
print(f"✅ Documento final criado com template + TOC + conteúdo + links")
except Exception as e:
raise RuntimeError(f"Erro na combinação final: {e}")
finally:
os.remove(temp_docx_path)
def _final_combination_strategy(self, content_docx_path):
"""Estratégia final que preserva template, TOC e links."""
# Carregar documentos
template_doc = Document(self.master_template_path)
content_doc = Document(content_docx_path)
print(f"📄 Template: {len(template_doc.paragraphs)} parágrafos")
print(f"📄 Conteúdo: {len(content_doc.paragraphs)} parágrafos")
# Verificar se o Pandoc gerou TOC
toc_found = False
for i, para in enumerate(content_doc.paragraphs[:10]):
if 'índice' in para.text.lower() or 'contents' in para.text.lower():
print(f"✅ TOC encontrado no Pandoc no parágrafo {i}")
toc_found = True
break
# Contar links no conteúdo
links_count = 0
for rel in content_doc.part.rels.values():
if "hyperlink" in str(rel.reltype):
links_count += 1
print(f"🔗 Links encontrados no conteúdo: {links_count}")
# ESTRATÉGIA: Usar o conteúdo do Pandoc como base e adicionar branding do template
final_doc = content_doc # Usar o documento com TOC e links
# Extrair elementos de branding do template (logos, cabeçalhos, etc.)
template_elements = []
for para in template_doc.paragraphs:
if para.text.strip() and para.text.strip() != "{{conteudo_guia}}":
template_elements.append(para.text)
if template_elements:
print(f"📋 Elementos de branding encontrados: {len(template_elements)}")
# Adicionar elementos do template no início
for i, element_text in enumerate(reversed(template_elements)):
if element_text.strip():
new_para = final_doc.paragraphs[0]._element
new_para = final_doc.element.body.insert(0, new_para)
# Criar novo parágrafo
p = final_doc.add_paragraph(element_text)
# Mover para o início
final_doc.element.body.insert(0, p._element)
# Adicionar quebra de página após elementos do template
if final_doc.paragraphs:
last_template_para = final_doc.paragraphs[len(template_elements)]
run = last_template_para.add_run()
run.add_break(WD_BREAK.PAGE)
# Guardar documento final
final_doc.save(self.output_docx_path)
print(f"💾 Documento final: template + TOC + conteúdo + {links_count} links")
def _convert_to_pdf(self):
"""Converte DOCX para PDF."""
print(f"A converter '{self.output_docx_path.name}' para PDF...")
try:
subprocess.run(
['libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', str(PDF_OUTPUT_DIR), str(self.output_docx_path)],
check=True, capture_output=True, text=True
)
print(f"✅ PDF criado: {self.output_pdf_path}")
except Exception as e:
print(f"Aviso: Erro na conversão para PDF: {e}")
def main():
"""Ponto de entrada principal."""
parser = argparse.ArgumentParser(description='Converte ficheiros Markdown para DOCX e PDF com branding.')
parser.add_argument('path', type=str, help='Caminho para o ficheiro .md ou pasta.')
args = parser.parse_args()
check_dependencies()
target_path = Path(args.path)
if not target_path.exists():
sys.exit(f"Erro: O caminho '{target_path}' não existe.")
files_to_process = []
if target_path.is_dir():
files_to_process.extend(sorted(target_path.glob("*.md")))
elif target_path.is_file() and target_path.suffix == '.md':
files_to_process.append(target_path)
if not files_to_process:
print("Nenhum ficheiro .md encontrado.")
return
print(f"Encontrados {len(files_to_process)} ficheiro(s).\n")
for md_file in files_to_process:
print("-" * 50)
DOCXConverter(md_file).convert()
if __name__ == '__main__':
main()