150 lines
5.9 KiB
Python
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 |