ParamManagerScripts/backend/script_groups/ImportHTML/utils/docx_converter.py

150 lines
5.9 KiB
Python

# 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