Spaces:
Runtime error
Runtime error
| """ | |
| Formatador de saída Markdown. | |
| Este módulo contém funções e classes para formatar documentos | |
| processados em formato Markdown. | |
| """ | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Any, Optional | |
| from utils.logger import get_logger | |
| # Logger para este módulo | |
| logger = get_logger(__name__) | |
| def format_to_markdown( | |
| processed_data: dict[str, Any], | |
| include_metadata_header: bool = True, | |
| include_tables: bool = True | |
| ) -> str: | |
| """ | |
| Formata dados processados em Markdown. | |
| Args: | |
| processed_data: Dados retornados pelo DoclingProcessor. | |
| include_metadata_header: Se deve incluir cabeçalho com metadados. | |
| include_tables: Se deve incluir tabelas formatadas. | |
| Returns: | |
| String Markdown formatada. | |
| """ | |
| document = processed_data.get("document") | |
| metadata = processed_data.get("metadata", {}) | |
| tables = processed_data.get("tables", []) | |
| language = processed_data.get("language", "desconhecido") | |
| sections = [] | |
| # Cabeçalho com metadados | |
| if include_metadata_header: | |
| header = _format_metadata_header(metadata, language) | |
| if header: | |
| sections.append(header) | |
| # Conteúdo principal do documento | |
| if document: | |
| try: | |
| if hasattr(document, "export_to_markdown"): | |
| content = document.export_to_markdown() | |
| if content: | |
| sections.append(content) | |
| except Exception as e: | |
| logger.warning(f"Erro ao exportar Markdown: {e}") | |
| sections.append(f"> ⚠️ Erro ao exportar conteúdo: {e}") | |
| # Tabelas (se não foram incluídas no export padrão) | |
| if include_tables and tables: | |
| tables_section = _format_tables_section(tables) | |
| if tables_section: | |
| sections.append(tables_section) | |
| return "\n\n---\n\n".join(sections) | |
| def _format_metadata_header( | |
| metadata: dict[str, Any], | |
| language: str | |
| ) -> str: | |
| """ | |
| Formata cabeçalho com metadados. | |
| Args: | |
| metadata: Dicionário de metadados. | |
| language: Código do idioma. | |
| Returns: | |
| String Markdown com metadados. | |
| """ | |
| lines = [] | |
| # Título | |
| titulo = metadata.get("titulo", metadata.get("nome_arquivo", "Documento")) | |
| lines.append(f"# {titulo}") | |
| lines.append("") | |
| # Metadados como lista | |
| meta_items = [] | |
| if metadata.get("autor"): | |
| meta_items.append(f"**Autor:** {metadata['autor']}") | |
| if metadata.get("data_criacao"): | |
| meta_items.append(f"**Data de criação:** {metadata['data_criacao']}") | |
| if language and language not in ("desconhecido", "erro", "nao_detectado"): | |
| lang_names = { | |
| "pt": "Português", | |
| "en": "Inglês", | |
| "es": "Espanhol", | |
| "fr": "Francês", | |
| "de": "Alemão", | |
| "it": "Italiano", | |
| } | |
| lang_name = lang_names.get(language, language.upper()) | |
| meta_items.append(f"**Idioma:** {lang_name}") | |
| if metadata.get("num_paginas"): | |
| meta_items.append(f"**Páginas:** {metadata['num_paginas']}") | |
| if metadata.get("num_tabelas"): | |
| meta_items.append(f"**Tabelas:** {metadata['num_tabelas']}") | |
| if metadata.get("num_imagens"): | |
| meta_items.append(f"**Imagens:** {metadata['num_imagens']}") | |
| if meta_items: | |
| lines.extend(meta_items) | |
| lines.append("") | |
| return "\n".join(lines) | |
| def _format_tables_section(tables: list[dict[str, Any]]) -> str: | |
| """ | |
| Formata seção de tabelas. | |
| Args: | |
| tables: Lista de tabelas extraídas. | |
| Returns: | |
| String Markdown com tabelas. | |
| """ | |
| if not tables: | |
| return "" | |
| lines = ["## Tabelas Extraídas", ""] | |
| for table in tables: | |
| index = table.get("indice", 0) | |
| lines.append(f"### Tabela {index}") | |
| lines.append("") | |
| # Se tem dados como dict/list, formata como tabela MD | |
| if table.get("dados"): | |
| md_table = _dict_to_markdown_table(table["dados"]) | |
| lines.append(md_table) | |
| elif table.get("markdown"): | |
| lines.append(table["markdown"]) | |
| elif table.get("texto"): | |
| lines.append(f"```\n{table['texto']}\n```") | |
| else: | |
| lines.append("*Dados da tabela não disponíveis*") | |
| lines.append("") | |
| return "\n".join(lines) | |
| def _dict_to_markdown_table(data: list[dict[str, Any]]) -> str: | |
| """ | |
| Converte lista de dicionários em tabela Markdown. | |
| Args: | |
| data: Lista de dicionários (cada dict = uma linha). | |
| Returns: | |
| String com tabela em formato Markdown pipe. | |
| """ | |
| if not data: | |
| return "*Tabela vazia*" | |
| # Pega colunas do primeiro item | |
| headers = list(data[0].keys()) | |
| lines = [] | |
| # Cabeçalho | |
| header_line = "| " + " | ".join(str(h) for h in headers) + " |" | |
| lines.append(header_line) | |
| # Separador | |
| separator = "| " + " | ".join("---" for _ in headers) + " |" | |
| lines.append(separator) | |
| # Dados | |
| for row in data: | |
| values = [] | |
| for h in headers: | |
| value = row.get(h, "") | |
| # Escapa pipes no conteúdo | |
| value = str(value).replace("|", "\\|") | |
| # Remove quebras de linha | |
| value = value.replace("\n", " ") | |
| values.append(value) | |
| row_line = "| " + " | ".join(values) + " |" | |
| lines.append(row_line) | |
| return "\n".join(lines) | |
| class MarkdownFormatter: | |
| """ | |
| Classe para formatação Markdown com configurações personalizadas. | |
| Permite manter configurações consistentes entre múltiplas formatações. | |
| """ | |
| def __init__( | |
| self, | |
| include_metadata_header: bool = True, | |
| include_tables: bool = True, | |
| include_toc: bool = False, | |
| max_heading_level: int = 6 | |
| ): | |
| """ | |
| Inicializa o formatador Markdown. | |
| Args: | |
| include_metadata_header: Se deve incluir cabeçalho com metadados. | |
| include_tables: Se deve incluir tabelas extraídas. | |
| include_toc: Se deve incluir sumário (Table of Contents). | |
| max_heading_level: Nível máximo de heading a usar. | |
| """ | |
| self.include_metadata_header = include_metadata_header | |
| self.include_tables = include_tables | |
| self.include_toc = include_toc | |
| self.max_heading_level = max_heading_level | |
| def format(self, processed_data: dict[str, Any]) -> str: | |
| """ | |
| Formata dados processados em Markdown. | |
| Args: | |
| processed_data: Dados do DoclingProcessor. | |
| Returns: | |
| String Markdown formatada. | |
| """ | |
| content = format_to_markdown( | |
| processed_data, | |
| include_metadata_header=self.include_metadata_header, | |
| include_tables=self.include_tables | |
| ) | |
| if self.include_toc: | |
| toc = self._generate_toc(content) | |
| if toc: | |
| content = f"{toc}\n\n---\n\n{content}" | |
| return content | |
| def _generate_toc(self, content: str) -> str: | |
| """ | |
| Gera sumário (Table of Contents) do conteúdo. | |
| Args: | |
| content: Conteúdo Markdown. | |
| Returns: | |
| String com sumário em Markdown. | |
| """ | |
| import re | |
| lines = [] | |
| lines.append("## Sumário") | |
| lines.append("") | |
| # Encontra headings | |
| heading_pattern = r"^(#{1,6})\s+(.+)$" | |
| for line in content.split("\n"): | |
| match = re.match(heading_pattern, line) | |
| if match: | |
| level = len(match.group(1)) | |
| title = match.group(2) | |
| if level <= self.max_heading_level: | |
| # Cria link | |
| anchor = self._slugify(title) | |
| indent = " " * (level - 1) | |
| lines.append(f"{indent}- [{title}](#{anchor})") | |
| return "\n".join(lines) if len(lines) > 2 else "" | |
| def _slugify(self, text: str) -> str: | |
| """ | |
| Converte texto em slug para anchor. | |
| Args: | |
| text: Texto a converter. | |
| Returns: | |
| Slug do texto. | |
| """ | |
| import re | |
| # Converte para lowercase | |
| slug = text.lower() | |
| # Remove caracteres especiais | |
| slug = re.sub(r"[^\w\s-]", "", slug) | |
| # Substitui espaços por hífens | |
| slug = re.sub(r"\s+", "-", slug) | |
| return slug | |
| def save_markdown( | |
| content: str, | |
| output_path: str | Path, | |
| encoding: str = "utf-8" | |
| ) -> Path: | |
| """ | |
| Salva conteúdo Markdown em arquivo. | |
| Args: | |
| content: String Markdown. | |
| output_path: Caminho do arquivo de saída. | |
| encoding: Encoding do arquivo. | |
| Returns: | |
| Path para o arquivo salvo. | |
| """ | |
| output_path = Path(output_path) | |
| output_path.write_text(content, encoding=encoding) | |
| logger.debug(f"Markdown salvo: {output_path}") | |
| return output_path | |