# utils/docx_converter.py import os import re import mammoth from pathlib import Path from models.pagina_html import PaginaHTML def procesar_docx(ruta_archivo, dir_adjuntos): """ Procesa un archivo DOCX y lo convierte directamente a Markdown, extrayendo las imágenes al directorio de adjuntos. """ # Asegurar que el directorio de adjuntos existe os.makedirs(dir_adjuntos, exist_ok=True) # Lista para almacenar imágenes procesadas imagenes_procesadas = [] def manejar_imagen(image): """Procesa cada imagen encontrada en el documento DOCX.""" try: # Generar nombre de archivo para la imagen extension = image.content_type.split('/')[-1] if hasattr(image, 'content_type') else 'png' if extension == 'jpeg': extension = 'jpg' # Usar alt_text si está disponible o generar nombre basado en índice filename = (image.alt_text if hasattr(image, 'alt_text') and image.alt_text else f"image-{len(imagenes_procesadas)+1}.{extension}") # Asegurar que el nombre sea válido para el sistema de archivos filename = re.sub(r'[<>:"/\\|?*]', "_", filename) if not filename.endswith(f".{extension}"): filename = f"{filename}.{extension}" # Ruta completa para guardar la imagen image_path = os.path.join(dir_adjuntos, filename) # Verificar si el objeto imagen tiene el atributo 'content' if hasattr(image, 'content') and image.content: # Guardar la imagen with open(image_path, 'wb') as f: f.write(image.content) # Agregar a la lista de imágenes procesadas if filename not in imagenes_procesadas: imagenes_procesadas.append(filename) # Retornar el formato para Obsidian return {"src": filename} else: # Si no hay contenido, registrar el problema print(f"Advertencia: No se pudo extraer contenido de imagen '{filename}'") # Retornar un marcador de posición return {"src": "imagen_no_disponible.png"} except Exception as e: print(f"Error procesando imagen en DOCX: {str(e)}") # En caso de error, retornar un marcador de texto return {"alt": "Imagen no disponible"} try: # Configurar opciones de conversión personalizada para manejar casos problemáticos options = { "ignore_empty_paragraphs": True, "style_map": "p[style-name='Heading 1'] => h1:fresh" } # Abrir el archivo DOCX with open(ruta_archivo, "rb") as docx_file: # Convertir con manejo de errores mejorado result = mammoth.convert_to_markdown( docx_file, convert_image=mammoth.images.img_element(manejar_imagen), options=options ) # Extraer el título (primera línea como encabezado) markdown_content = result.value lines = markdown_content.strip().split('\n') titulo = None # Buscar el primer encabezado para usar como título for line in lines: if line.startswith('#'): # Eliminar los símbolos # y espacios titulo = re.sub(r'^#+\s*', '', line).strip() break # Si no hay encabezado, usar el nombre del archivo if not titulo: titulo = Path(ruta_archivo).stem # Mostrar advertencias de la conversión warnings = result.messages if warnings: print(f"Advertencias en la conversión:") for warning in warnings: print(f"- {warning}") # Post-procesar para formato Obsidian markdown_content = post_procesar_markdown_obsidian(markdown_content, imagenes_procesadas) # Crear objeto PaginaHTML return PaginaHTML( ruta_archivo=ruta_archivo, titulo=titulo, contenido=markdown_content, imagenes=imagenes_procesadas ) except Exception as e: print(f"Error procesando DOCX {ruta_archivo}: {str(e)}") return PaginaHTML( ruta_archivo=ruta_archivo, contenido=f"Error al procesar: {str(e)}" ) def post_procesar_markdown_obsidian(markdown, imagenes): """ Realiza ajustes adicionales al markdown para formato Obsidian. """ # 1. Convertir referencias de imágenes al formato Obsidian for imagen in imagenes: # Buscar referencias de imágenes en formato estándar # y convertirlas al formato Obsidian markdown = re.sub( r'!\[(.*?)\]\(' + re.escape(imagen) + r'\)', f'![[{imagen}]]', markdown ) # 2. Agregar salto de línea adicional después de cada encabezado markdown = re.sub(r'(^|\n)(#+\s.*?)(\n(?!\n))', r'\1\2\n\3', markdown) # 3. Arreglar listas mal formadas (asegurar espacio después de * o -) markdown = re.sub(r'(^|\n)([*\-])([^\s])', r'\1\2 \3', markdown) # 4. Mejorar formato de tablas # Asegurar que hay línea en blanco antes y después de tablas markdown = re.sub(r'([^\n])\n(\|[^\n]+\|)', r'\1\n\n\2', markdown) markdown = re.sub(r'(\|[^\n]+\|)\n([^\n\|])', r'\1\n\n\2', markdown) # 5. Normalizar fin de líneas markdown = markdown.replace('\r\n', '\n') # 6. Eliminar líneas en blanco consecutivas excesivas markdown = re.sub(r'\n{3,}', '\n\n', markdown) return markdown