Mostrando entradas con la etiqueta python-docx. Mostrar todas las entradas
Mostrando entradas con la etiqueta python-docx. Mostrar todas las entradas

martes, 5 de mayo de 2026

DATOS. Archivos de texto

Archivos .docx

Acceso a tablas y textos

Dado que ya hemos hablado de los archivos de texto .docx (por ejemplo, aquí), resulta un poco complicado plantear esta entrada sin que resulte repetitiva, aunque el intento no carece de sentido. En aquel momento hablamos de los documentos .docx desde una perspectiva de generación de los mismos (aunque se trataran también otros temas), y ahora nuestro interés se centra en la obtención de datos de documentos ya creados, fundamentalmente datos textuales y contenidos contenidos en tablas, lo que equivale a decir datos no estructurados y datos que aspiran a ser considerados como datos estructurados, pero que aun no alcanzan este nivel de organización (datos semi-estructurados).

La biblioteca python-docx, ya presentada en relación con la temática antes señalada será también la herramienta que nos ayude a acceder al contenido de los documentos .docx, que son una evolución compleja de los .doc primitivos, implementando la arquitectura XML, lo que facilita el manejo del contenido mediate script Python, que se hace precisamente manejando esos componentes xml.

A nosotros lo que realmente nos importa ahora, desde la perspectiva de esta subsección, es el acceso al contenido, diferenciando si este se presenta en formato de tabla (mejor tabla-formulario), que, como dije, se considera como dato semi-estructurado, aunque no siempre lo sea, frente al texto no-tabla (párrafos), que se consideran datos no estructurados. Respecto a las tablas, dado que también se emplean para formatear la presentación del contenido, en algunas ocasiones éste no es en realidad un dato semi-estructurado; se trata de un segmento textual no estructuardo y como tal debe ser tratado. A veces esta diferencia es de detalle, lo que da lugar a confusiones en el planeamiento de cómo debe ser tratado en los procesos de limpieza y de análisis.

No es esta una cuestión que sólo afecte a los documentos .docx, pero lo trataremos aquí para avanzar propuestas, dado que es una cuestión de importancia dentro de la subsección en la que nos encontramos en este punto.Pero antes de abordar esta cuestión resolvamos primero las más básicas: el acceso al contenido en su conjunto y el acceso al contenido de forma diferenciada: tablas y textos.



import docx
import os

def leer_contenido_docx(ruta_archivo):
    if not os.path.exists(ruta_archivo):			    		# Verificar si el archivo existe
        print(f"Error: El archivo '{ruta_archivo}' no existe.")
        return
    doc = docx.Document(ruta_archivo)					  		# Cargar el documento

    print(f"--- INICIO DEL CONTENIDO: {ruta_archivo} ---\n")
    
    for elemento in doc.element.body:							# Acceso secuencial a todos los elementos del cuerpo del documento
        if isinstance(elemento, docx.oxml.text.paragraph.CT_P): # 1. Si el elemento es un párrafo (CT_P)
            p = docx.text.paragraph.Paragraph(elemento, doc)
            if p.text.strip(): # Evitar mostrar líneas vacías si se desea
                print(f"[PÁRRAFO]: {p.text}")
        elif isinstance(elemento, docx.oxml.table.CT_Tbl):		# 2. Si el elemento es una tabla (CT_Tbl)
            tabla = docx.table.Table(elemento, doc)
            print(f"\n[TABLA INICIO - {len(tabla.rows)} filas x {len(tabla.columns)} columnas]")
            for fila in tabla.rows:
                contenido_fila = [celda.text.strip() for celda in fila.cells]  # Extraemos el texto de cada celda
                print(" | ".join(contenido_fila))
            print("[TABLA FIN]\n")

    print(f"\n--- FIN DEL DOCUMENTO ---")
    
# --- Llamada a la función ---

if __name__ == "__main__":
    nombre_archivo = r"Directorio_de_mi_archivo.docx" 	# Aquí la ruta de tu_archivo.docx'
    leer_contenido_docx(nombre_archivo)


Este script permite acceder a un documento .docx, identifica su contenido, diferenciando tablas de párrafos y muestra todo ese contenido en el CMD señalando la diferencia entre el procedente de una tabla del identificado como párrafo. Esta diferenciación ya señala la posibilidad de acceder a datos semi-estructurado (tablas y tablas-formulario) y a datos no estructurados (párrafos) de forma diferenciada. El siguiente script permite el acceso a las tablas del documento y a su contenido. A partir de aquí podríamos desarrollar procedimientos específicos de acceso a datos concretos. La idea a posteriori es facilitar la transformación de estos datos semi-estructurados en datos estructurados, para tratarlo con los procedimiento del organización y limpieza, y de análisis acordes con los datos de esa naturaleza.



import docx
import os

#Función principal: exploración de las tablas del documento

def leer_tablas_docx(ruta_archivo):
    if not os.path.exists(ruta_archivo):
        print(f"Error: El archivo '{ruta_archivo}' no existe.")
        return

    doc = docx.Document(ruta_archivo)

    print(f"--- RELACIÓN DE TABLAS ENCONTRADAS ---")
    
    tablas_limpias = []
    
    for i, tabla in enumerate(doc.tables):
        # Extraemos el nombre/título desde las propiedades XML de la tabla (tblPr)
        nombre_tabla = None
        try:
            # Buscamos en el elemento XML de la tabla el tag de título (tblCaption)
            nombre_tabla = tabla._element.xpath('.//w:tblCaption/@w:val')
            if nombre_tabla:
                nombre_tabla = nombre_tabla[0]
            else:
            # Intentamos con la descripción si el título falla
                nombre_tabla = tabla._element.xpath('.//w:tblDescription/@w:val')
                if nombre_tabla: nombre_tabla = nombre_tabla[0]
        except:
            nombre_tabla = None

        if not nombre_tabla:
            nombre_tabla = f"Tabla_{i+1}"
        
        tablas_limpias.append((nombre_tabla, tabla))
        print(f"ID: {i+1} | Nombre en Navegador: {nombre_tabla}")
    
    print("-" * 40 + "\n")

    for nombre, tabla in tablas_limpias:		# Mostramos el contenido de las tablas
        print(f"\n[CONTENIDO TABLA]: {nombre}")
        print("-" * (18 + len(nombre)))
        
        for fila in tabla.rows:
            textos_celdas = []			# Usamos un set para evitar celdas duplicadas en caso de celdas combinadas
            for celda in fila.cells:
                texto = celda.text.strip().replace('\n', ' ')
                textos_celdas.append(texto) 
            print(" | ".join(textos_celdas))
        print("-" * 20)
        
# --- Llamada a la función ---

if __name__ == "__main__":
    nombre_archivo = r"ruta_de_tu_archivo.docx" 	# Aquí tu ruta de archivo
    leer_tablas_docx(nombre_archivo)				# Llamada a la función


Mediante este otro script mostramos los párrafos (datos no estructurados), los cuales posteriormente deberan ser tratados con los procedimientos correspondiente. Es interesante observar que cada párrafo queda identificado por su posición, lo que puede facilitar su localización, aunque como procedimiento es poco funcional si pensamos en una obtención masiva de determinado contenido. De momento, algo es algo.



import docx
import os

def leer_texto_no_tabular(ruta_archivo):
    if not os.path.exists(ruta_archivo):				# Verificar si el archivo existe
        print(f"Error: El archivo '{ruta_archivo}' no existe.")
        return

    doc = docx.Document(ruta_archivo)		 			# Cargar el documento

    print(f"--- INICIO DEL CONTENIDO NO TABULAR: {ruta_archivo} ---\n")

    contador_parrafos = 0						 		# doc.paragraphs solo itera por los párrafos
    
    for i, para in enumerate(doc.paragraphs):
        texto = para.text.strip()						# Limpiamos espacios en blanco al inicio y final
        
        if texto:										# Omitir párrafos vacíos para una salida más limpia en CMD
            contador_parrafos += 1
            print(f"[{contador_parrafos}]: {texto}")	# Mostramos el índice del párrafo para facilitar el seguimiento
            print("-" * 10) # Separador visual opcional

    if contador_parrafos == 0:
        print("No se encontró texto fuera de tablas en este documento.")

    print(f"\n--- FIN DEL DOCUMENTO (Total párrafos: {contador_parrafos}) ---")

# --- Llamada a la función ---

if __name__ == "__main__":
    nombre_archivo = r"ruta_de_mi_documento.docx" 	 # Aquí tu archivo
    leer_texto_no_tabular(nombre_archivo)			 # Llamada a la función


miércoles, 22 de abril de 2026

DATOS. Limpieza de datos

Tablas Word (.docx) (II)

Conversión a Excel (xlsx)

Una vez que obtenemos las tablas de un documento .docx, una de las opciones de dar continuidad al procedimiento es transformar dichas tablas en un archivo .xlsx. Otra es hacerlo como.csv, pero la primera opción presenta ventajas sustantivas, como crear una estructura tabular clara para visualización directa, además de manejable desde un servicio de hoja de cálculo. Además incluye una ventaja más: los datos se almacenan como tipos de datos con formatos específicos (string, numéricos, fecha...), dado que así han sido procesados en el script (ver entrada anterior). Por todo esto, esta entrada es mera continuación de la precedente y aporta a aquella el archivo de las tablas y sus contenidos como hoja de cálculo .xlsx. Este es el script que lo hace posible:



# 0. Importamos la bibliotecas necesarias

import pandas as pd
from docx import Document
import os
import unicodedata
import warnings         #Importamos avisos para controlar...

warnings.simplefilter(action='ignore', category=FutureWarning)  # ... que los ocultamos explícitamente

# 1. Función para normalizar las cabeceras de las tablas y facilitar su tratamiento posterior

def normalizar_cabeceras(lista_cabeceras):
    nuevas_cabeceras = []
    for i, nombre in enumerate(lista_cabeceras):
        nombre = str(nombre).strip().lower()
        nombre = "".join(c for c in unicodedata.normalize('NFD', nombre) if unicodedata.category(c) != 'Mn')     # Limpieza básica
        nombre = nombre.replace(" ", "_").replace("/", "_") or f"columna_{i}"
        
        count = 0                            # Código para evitar duplicados (y el error 'duplicate keys') en el nombre de los campos
        temp_nombre = nombre
        while temp_nombre in nuevas_cabeceras:
            temp_nombre = f"{nombre}_{count}"
            count += 1
        nuevas_cabeceras.append(temp_nombre)
    return nuevas_cabeceras

# 2. Función para extraer los datos de las tablas evitando alteraciones previsibles (celdas combinadas)

def extraer_matriz_segura(tabla):
    matriz = []
    for fila in tabla.rows:                 # Extraer contenidos de las tablas evitando errores de celdas combinadas repetidas
        fila_datos = []
        for celda in fila.cells:
            fila_datos.append(celda.text.strip())
        matriz.append(fila_datos)
    
    if not matriz: return []                # Equilibrado de filas, dado que Pandas exige que todas midan lo mismo
    max_cols = max(len(f) for f in matriz)
    for f in matriz:
        while len(f) < max_cols:
            f.append("")                    # Rellenar huecos por celdas combinadas
    return matriz

# 3. Función para obtener las tablas del documento y su contenido

def extractor_universal(ruta_docx):
    try:
        doc = Document(ruta_docx)
        nombre_base = os.path.splitext(ruta_docx)[0]
        ruta_excel = f"{nombre_base}_DB.xlsx"
        lista_dfs = []

        print(f"=== ANALIZANDO: {ruta_docx} ===")

        for i, tabla in enumerate(doc.tables):
            matriz = extraer_matriz_segura(tabla)               # Llamada a función 2
            if len(matriz) < 1: continue

            cabeceras = normalizar_cabeceras(matriz[0])         # Normalizar cabeceras con función 1
            cuerpo = matriz[1:] if len(matriz) > 1 else []

            df = pd.DataFrame(cuerpo, columns=cabeceras)        # Crear el DataFrame de forma más segura
            
            for col in df.columns:                              # Convertir los datos numéricos
                df[col] = pd.to_numeric(df[col], errors='ignore')

            lista_dfs.append(df)
            print(f"Tabla {i+1}: Extraída con éxito ({df.shape[0]} registros).")
            print(df.head(3).to_string(index=False))

        if lista_dfs:                                            # Exportar a Excel, formato xlsx
            with pd.ExcelWriter(ruta_excel, engine='openpyxl') as writer:
                for idx, df in enumerate(lista_dfs):
                    df.to_excel(writer, sheet_name=f"Tabla_{idx+1}", index=False)
            print(f"\n[OK] Guardado en: {ruta_excel}")

    except Exception as e:
        print(f"\n[ERROR]: {e}")

# 4. Llamada a función principal

extractor_universal("tablas.docx")


Además de mostrar los datos por CMD, este script crea una hoja de cálculo Excel en formato .xlsx y almacena en ella las tablas en formato filas-coumnas y los datos que contienen, respetando el tipo de dato supuesto (string, numérico...). Esto tiene varias consecuencias para el tratamiento posterior:

  • se puede acceder a las tablas directamente desde la hoja de cálculo, pero también desde script mediante Pandas trabajando con los datos en función de su tipología.
  • el acceso se hace directamente a datos estructurados, dada la configuración de las tablas resultantes como tales, lo que facilita la manipulación de los datos mediante funciones asociadas a la biblioteca Pandas (y otras)

Además, mediante la función 1 (def normalizar_cabeceras():) evitamos posibles errores en el posterior tratamiento de las tablas. La segunda función (def extraer_matriz_segura()) permite extraer el contenido evitando los errores que derivan de la combinación de tablas.

Pero el núcleo del script sigue siendo la función principal (la 3, def extractor_universal(), que...

  • es a la que llamamdos desde el segmento de ejecución (4, extractor_universal("tablas.docx")), instrucción donde se pasa por parámetro el archivo .docx con el que trabajamos.
  • Esta función principal ejecuta el recorrido del documento, obtiene las tablas que contiene y sus datos...
  • ... asegurando su viabilidad mediante llamada a funciones auxiliares...
    • matriz = extraer_matriz_segura(tabla) y
    • cabeceras = normalizar_cabeceras(matriz[0]))
  • ... generando el dataframe (df = pd.DataFrame(cuerpo, columns=cabeceras))...
  • ... y crea el archivo .xlsx (with pd.ExcelWriter(ruta_excel, engine='openpyxl') as writer:)

El resultado es un .xlsx plenamente operativo, estructurado en hojas (una por tabla) que contienen tablas organizadas para que sus datos puedan ser tratados directamente como datos estructurados y que sea posible trabajar con ellos (consultar y manejo) manualmente, mediante lenguajes de macros o mediante python y sus bibliotecas.

Dada la simplicidad de contenido, pero sobre todo de organización de las tablas, el procedimiento de acceso a tablas y extracción de su contenido ha resultado satisfactorio, haciendo innecesario un tratamiento posterior para que sea posible trabajar directamente con el archivo resultante (.xlsx) y con las tablas que contiene.

DATOS. Limpieza de datos

Tablas Word (.docx) (I)

Acceso a tablas simples

El trabajo con textos .docx ya ha sido tratado en otras entradas de este blog, pero desde la prespectiva de la composición de textos. Ahora nos interesa el proceso inverso: la obtención de datos. Empezaremos por la identificación de tablas simples (estructura tabular básica) y la obtención de los datos que contienen.

El documento que nos sirve de modelo no tiene más que ese interés. Obviamente los datos son inventados y carecen de relevancia para cualquier otro objetivo. Se trata de un texto en soporte .docx, que contiene unos breves párrafos de texto (datos no estructurados) y tres tablas de estructura simple (asimilables al formato BD: fila (registro) - columna (campo/variable), que se comportan inicialmente como datos semi-estructurados, aunque sólo en función del soporte (de presentarse en un formato estructurado en campos podrían considerarse datos estructurados).

El primer objetivo que nos proponemos ahora es automatizar identificación de las tablas (obviando el texto) y la extracción de los datos que contienen. Para ello utilizamos el siguiente script Python:



# 0. Importamos las bibliotecas necesarias

import pandas as pd
from docx import Document
import warnings                     # Importamos el módulo de avisos

# 1. Instrucción para ignorar todos los avisos técnicos de las librerías

warnings.filterwarnings("ignore")

# 2. Función para identificación de los tipos de datos para posterior tratamiento

def identificar_tipos_datos(df):    # Se asume la posibilidad de distintos tipos de datos (fechas y numéricos)
    for col in df.columns:
        intentar_fecha = pd.to_datetime(df[col], errors='coerce')   # Fechas
        if intentar_fecha.notna().all():
            df[col] = intentar_fecha
            continue
        intentar_num = pd.to_numeric(df[col], errors='coerce')      # Numéricos
        if intentar_num.notna().sum() > len(df) * 0.8: 
            df[col] = intentar_num
    return df

# 3. Función para la extracción de datos de las tablas-docx

def extractor_universal(ruta_docx):
    try:
        doc = Document(ruta_docx)
        for i, tabla in enumerate(doc.tables):
            datos = []
            for fila in tabla.rows:
                datos.append([celda.text.strip() for celda in fila.cells])

            if not datos: continue

            cabecera = datos[0]
            cuerpo = datos[1:]
            df = pd.DataFrame(cuerpo, columns=cabecera)
            df_estructurado = identificar_tipos_datos(df)	 # Llamada a la función 2

            print(f"\n--- TABLA {i+1} ---")                  # Visualización de tablas en el CMD
            print(df_estructurado.to_string(index=False)) 
            print("-" * 40)

    except Exception as e:
        print(f"Error en el proceso: {e}")

# 4. Ejecución de la función principal

extractor_universal("tablas.docx")


Las pretensiones de este script son muchas, a pesar de lo limitado de su objetivo: se trata de:

  • Identificar las tablas presentes en el documento. Al tratarse de un documento docx se hace uso de la biblioteca python-docx
  • Se pretende crear un script de caracter universal, de ahí que se esperen distintos tipos de datos, los cuales se desean identificar para un posterior tratamiento que no se da en este caso. Esto explica la presencia de la función def identificar_tipos_datos(df) que se relaciona con el uso de la bibliteca import pandas as pd
  • Esto hace posible que se generen avisos no deseados y que carecen de utilidad inmediata, por eso se importa el módulo de control de avisos import warnings y se inhibe su aparición en el CMD con warnings.filterwarnings("ignore"), procedimiento que en otras ocasiones puede ser contraproducente, pero no en esta, ya que en realidad conocemos el contenido de las tablas
  • El núcleo central del script se plantea dentro del bucle for i, tabla in enumerate(doc.tables):, que es donde se obtiene el recuento de las tablas del documento, se recorren éstas, se extrae su contenido y se procesa, incluyendo su conversión de texto a diferentes tipos de datos. Además, finalmente, se muestran por pantalla.

    Dado que con esto finaliza el script, se pierden en realidad posibilidades de tratamiento que sí están disponibles en función de la conversión de datos y de la participación de Pandas; pero ahora lo que nos interesaba era la obtención de los datos, sólo los datos de las tablas, y este objetivo está conseguido. En posteriores entradas profundizaremos en el desarrollo de este proceso.

domingo, 29 de marzo de 2026

DATOS. Directorios y archivos

Unificar la extensión

Hay ocasiones en las que nos interesa unificar el tipo de documentos a fin de simplificar procesos posteriores. En estos casos disponer de un procedimiento que transforma todos los arhcivos a un mismo tipo es una buena solución.

Cuando la procedencia de los archivos es diversa suele suceder que acabamos trbajando con documentos de contenido similar (o igual) pero en diferentes formatos. Un ejemplo de ello son los archivos derivados del uso de procesadores de texto en los que incluso usando el mismo servicio (MSO-Word) podemos generar documentos .doc o documentos .docx. Si a esto añadimos el uso de LO-Writer obtendremos una colección de archivos en tres formatos diferentes: .doc, .docx y .odt. Cierto que LO-Writer permite acceder a todos ellos indistintamente y de forma fiable y segura, pero si queremos automatizar determinados procesos desde Python unificar la tipología de los archivos puede considerarse una media previa cuanto menos conveniente.

El script que sigue permite unificar todos los archivos de una ruta al formato .docx, lo que facilita el posterior tratamiento de todos los archivos con las funciones disponibles en la biblioteca python-docx.




#Bibliotecas necesarias ---------------------------------------------------

import os
import subprocess
import pathlib

#Función -------------------------------------------------------------------

def convertir_y_limpiar(ruta_absoluta):
    # Configuración de ruta
    RUTA_LIBREOFFICE = r'C:\Program Files\LibreOffice\program\soffice.exe'
    
    if not os.path.exists(RUTA_LIBREOFFICE):
        print(f"Error: No se encontró LibreOffice en {RUTA_LIBREOFFICE}")
        return

    extensiones_a_convertir = {'.doc', '.odt'}
    
    if not os.path.exists(ruta_absoluta):
        print(f"Error: La ruta {ruta_absoluta} no existe.")
        return

    print(f"Procesando archivos en: {ruta_absoluta}...\n")
    
    for raiz, dirs, archivos in os.walk(ruta_absoluta):
        for nombre_archivo in archivos:
            ruta_original = pathlib.Path(os.path.join(raiz, nombre_archivo))
            ext = ruta_original.suffix.lower()

            # 1. Ignorar si ya es .docx
            if ext == '.docx':
                continue

            # 2. Procesar solo .doc y .odt
            if ext in extensiones_a_convertir:
                # Definimos cómo se llamaría el nuevo archivo
                ruta_nuevo_docx = ruta_original.with_suffix('.docx')
                
                print(f"Transformando: {nombre_archivo} -> {ruta_nuevo_docx.name}")
                
                try:
                    # Ejecutar conversión
                    resultado = subprocess.run([
                        RUTA_LIBREOFFICE, 
                        '--headless', 
                        '--convert-to', 'docx', 
                        '--outdir', str(ruta_original.parent),
                        str(ruta_original)
                    ], check=True, capture_output=True)

                    # 3. Verificación de seguridad: ¿Existe el nuevo archivo?
                    if ruta_nuevo_docx.exists():
                        # Eliminamos el original solo si el nuevo ya existe
                        os.remove(ruta_original)
                        print(f"   [OK] Convertido y eliminado original.")
                    else:
                        print(f"   [!] Error: No se encontró el archivo convertido {ruta_nuevo_docx.name}")

                except Exception as e:
                    print(f"   [X] Error procesando {nombre_archivo}: {e}")

    print("\n¡Limpieza y conversión completadas!")

#Script de aplicación de función ---------------------------------------------------------------------

if __name__ == "__main__":
    mi_ruta = "Mi_Ruta"
    convertir_y_limpiar(mi_ruta)


Este script convierte archivos de tipo extensiones_a_convertir = {'.doc', '.odt'} a archivos .docx usando LO RUTA_LIBREOFFICE = r'C:\Program Files\LibreOffice\program\soffice.exe'. No es la única opción, pero sí muy recomendable (y gratuita), motivo por el que insisto en el interés que tiene trabajar con LibreOffice también para automatizar procesos con Python.

En este caso el script susituye los archivos originales por los convertidos, por lo que deberás hacer una copia de seguirdad de aquellos si los quieres. Este otro script mantiene los archivos originales y genera un nuevo directorio donde archiva los convertidos



#---Bibliotecas necesarias ------------------------------------------------

import os
import subprocess
import pathlib

#---Función de conversión -------------------------------------------------

def convertir_a_nueva_carpeta(ruta_origen, carpeta_destino):
    # 1. Configuración de rutas
    RUTA_LIBREOFFICE = r'C:\Program Files\LibreOffice\program\soffice.exe'
    
    if not os.path.exists(RUTA_LIBREOFFICE):
        print(f"Error: No se encontró LibreOffice.")
        return

    # 2. Crear la carpeta de destino si no existe
    ruta_destino_abs = pathlib.Path(carpeta_destino).resolve()
    if not ruta_destino_abs.exists():
        os.makedirs(ruta_destino_abs)
        print(f"Carpeta creada: {ruta_destino_abs}")

    extensiones_a_convertir = {'.doc', '.odt'}

    print(f"Leyendo de: {ruta_origen}")
    print(f"Guardando en: {ruta_destino_abs}\n")
    
    for raiz, dirs, archivos in os.walk(ruta_origen):
        for nombre_archivo in archivos:
            ruta_original = pathlib.Path(os.path.join(raiz, nombre_archivo))
            ext = ruta_original.suffix.lower()

            if ext in extensiones_a_convertir:
                print(f"Convertiendo: {nombre_archivo}...")
                
                try:
                    # 3. Ejecutar conversión apuntando al NUEVO directorio
                    subprocess.run([
                        RUTA_LIBREOFFICE, 
                        '--headless', 
                        '--convert-to', 'docx', 
                        '--outdir', str(ruta_destino_abs), # <--- AQUÍ está el cambio clave
                        str(ruta_original)
                    ], check=True, capture_output=True)

                    # 4. Verificación (Opcional, pero recomendada)
                    nuevo_archivo = ruta_destino_abs / (ruta_original.stem + ".docx")
                    if nuevo_archivo.exists():
                        print(f"   [OK] Guardado en destino.")
                    
                except Exception as e:
                    print(f"   [X] Error con {nombre_archivo}: {e}")

    print("\n¡Proceso finalizado! Los archivos originales permanecen intactos.")

# --- Llamada a la función ------------------------------------------------------------

if __name__ == "__main__":
    origen = "Mi_Ruta_Documentos"  # Carpeta donde están tus .doc/.odt
    destino = "Documentos_Convertidos" # Nueva carpeta donde se guardarán
    convertir_a_nueva_carpeta(origen, destino)


La conversión a .docx no es la única posible, y en muchos casos tampoco la más adecuada, siendo .pdf la extensión preferida para realizar determinadas transaciones o el almacenamiento de documentos. Además también disponemos de biblitecas Python para trabajar con estos documentos. Por ello te proporciono un tercer y último script que convierte archivos procedentes de los servicios de procesador de texto a .pdf tomando a LibreOffice como tecnología de base.



#---Bibliotecas necesarias ------------------------------------------------

import os
import subprocess
import pathlib

#---Función de conversión -------------------------------------------------

def generar_pdfs_en_destino(ruta_origen, carpeta_pdf):
    # --- Configuración ---
    RUTA_LIBREOFFICE = r'C:\Program Files\LibreOffice\program\soffice.exe'
    
    if not os.path.exists(RUTA_LIBREOFFICE):
        print(f"Error: LibreOffice no detectado en {RUTA_LIBREOFFICE}")
        return

    # Extensiones que queremos convertir a PDF
    extensiones_admitidas = {'.doc', '.docx', '.odt'}

    # Crear carpeta de destino absoluta
    ruta_salida = pathlib.Path(carpeta_pdf).resolve()
    if not ruta_salida.exists():
        os.makedirs(ruta_salida)
        print(f"Carpeta de salida creada: {ruta_salida}")

    print(f"Iniciando conversión de documentos a PDF...")
    print(f"Origen: {ruta_origen}\n" + "-"*30)

    # --- Procesamiento ---
    for raiz, dirs, archivos in os.walk(ruta_origen):
        for nombre_archivo in archivos:
            ruta_archivo_original = pathlib.Path(os.path.join(raiz, nombre_archivo))
            ext = ruta_archivo_original.suffix.lower()

            if ext in extensiones_admitidas:
                print(f"Procesando: {nombre_archivo}...")
                
                try:
                    # Ejecutar LibreOffice para exportar a PDF
                    subprocess.run([
                        RUTA_LIBREOFFICE,
                        '--headless',
                        '--convert-to', 'pdf',
                        '--outdir', str(ruta_salida),
                        str(ruta_archivo_original)
                    ], check=True, capture_output=True)

                    # Verificación de que el PDF se creó
                    nombre_esperado = ruta_archivo_original.stem + ".pdf"
                    if (ruta_salida / nombre_esperado).exists():
                        print(f"   [OK] PDF generado correctamente.")
                    else:
                        print(f"   [!] Advertencia: No se confirmó la creación de {nombre_esperado}")

                except Exception as e:
                    print(f"   [X] Error al convertir {nombre_archivo}: {e}")

    print("\n" + "-"*30 + "\n¡Proceso completado! Los archivos originales no han sido modificados.")

# --- Ejecución den la función -----------------------------------------------------------------------

if __name__ == "__main__":
    # Define tus rutas aquí
    mi_directorio_original = "Documentos_Trabajo" 
    mi_directorio_pdfs = "Archivo_Exportado_PDF"
    
    generar_pdfs_en_destino(mi_directorio_original, mi_directorio_pdfs)


jueves, 3 de abril de 2025

Evaluación. Propuestas.

Modelo básico en Python



Dentro de las posibles situaciones en que se puede requerir automatizar la evaluación, voy a considerar en esta entrada la más básica en términos de desarrollo del algoritmo, de ahí su título.


Supongamos las siguientes condiciones como punto de partida: se trata de recoger los datos de una prueba individual aplicada a un alumno de la que no se requiere almacenar más información que la que deriva de un análisis simple de los datos. En esta situación, que es más simple que la que planteamos en el [docap-modelo],  vamos a necesitar un script (python) para la recogida de datos, la corrección de la prueba, el análisis de resultados y su posterior traslado a un documento de texto a modo de informe. Para crear el informe voy a usar la biblioteca python-docx, pero, en función del carácter básico del modelo, me limitaré a sus componentes más básicos. Resumiendo, que a fuerza de ser todo básico, esto es lo mínimo que se puede hacer.

Después de importar la librería python-docx (from docx import Document) adelantándonos a lo que será la fase final del proceso, empezamos por diferenciar las distintas partes de que se compone este proyecto, esquema que nos puede ser de mucha utilidad para otras ocasiones:
  • Procedimientos para la recogida de datos en base a variables y listas de datos. Diferencio el procedimiento de recogida de datos personales del de recogida de respuestas a los ítem. Veremos posteriormente esta diferencia con más detalle.
  • Procedimientos de corrección de la prueba y de análisis (simple) de los resultados.
  • Y procedimientos para la elaboración del informe (soporte documento Word)
Debo explicar que, para facilitar la comparación entre los recursos y también para simplificar la elaboración de estas entradas, he tomado como referencia y ejemplo el docap modelo explicado en [esta entrada], incluyendo el formulario empleado en esa ocasión y su estructura.

Sobre esa base, en la recogida de datos, como dije antes se diferencian dos procedimientos: el que se requiere para los datos de identificación y el necesario para las respuestas a los ítem. Esta diferencia es doble, ya que afecta tanto al sistema de referenciación (variables vs. colecciones de datos) como al procedimiento de recogida (input() vs. ciclo for). Me explico y concreto
  • Para recoger los datos de identificación utilizo variables y hago uso de la función input() (vg. nombre = input('Nombre: '))
  • Pero para recoger las respuestas a los ítem, primero creo una lista vacía (respuestas =[]) que después doto de contenido mediante un bucle (for i in range(0,7):) y la función append() (respuestas.append(item)) que se aplica tras solicitar la entrada de datos, eso sí, también mediante input() (item =input('Ítem ' + str(i+1) + ":  "))
La segunda base (corrección y análisis) también se puede dividir en dos subfases, siendo la primera de ellas la corrección de la prueba y la segunda el cálculo de resultados en función de las referencias usadas (que son las mismas que en el docap). veamos con cierto detalle cada una de ellas, empezando por la primera ya que presenta cierta complejidad.
  • En primer lugar debemos contar con una lista que contenga las respuestas correctas (soluciones =['A','B','C','B','A','C','D']) así declarar una tercera lista donde guardar las puntuaciones que resulten de la corrección de los ítem (pd =[])
  • La puntuación del ítem implica la comparación de la respuesta obtenida anteriormente con la esperada y referenciada en la lista soluciones[].
  • Para realizar esta comparación deberemos formular un segundo bucle (for i in range(0,7):) que contenga un condicional en el cual se comparan ambas listas (if respuestas[i] == soluciones[i]:) y se genera el valor de la puntuación  y su alternativa (p = 1) (else: -> p = 0)
  • Concluimos el proceso con la función append() (pd.append(p)) para dotar de contenido a la lista pd[]
La segunda es mucho más simple y reproduce los mismos cálculos que vimos en OOo Basic: (recuérdese que se trata de un simple ejercicio, por lo que se simplifican en extremo los cálculos y el análisis)
  • Mediante un bucle (for i in range(0,7):) establecemos el sumatorio de la puntuación directa (pdt = pdt + pd[i])
  • Y partiendo de ésta y de los valores media (med = 5.38) y desviación típica (dt = 0.47), calculamos el porcentaje de aciertos (pc = (pdt/7)*100) y la puntuación típica (pz = (pdt - med) / dt)
Finalizamos este script implementado el código que nos permite generar el documento-informe, haciendo uso, en este caso, de la biblioteca python-docx, concretamente las funciones de crear (informe = Document()) y grabar el documento (informe.save('C:/PON_AQUÍ_TUS_DIRECTORIOS/Info'+ nombre +'.docx')), incorporando como contenido un título (informe.add_heading('Prueba de conocimientos básicos', level = 0)), una breve identificación del sujeto (vg. informe.add_paragraph('Datos personales del alumno: ' + nombre + ' ' + apellidos)) y la síntesis de los resultados obtenidos, ésta mediante un listado (vg. informe.add_paragraph('Puntuación directa: ' + str(pdt), style='List Bullet')

Obsérvese que se ha personalizado el identificador del informe empleando el contenido de la variable nombre ('Info'+ nombre +'.docx'). Esto permite generar diferentes informes siempre que no coincidan los nombres de los alumnos, lo que no es suficiente, siendo necesario idear sistemas más complejos para evitar las consecuencias no deseadas que derivan de esa coincidencia. Pero para nuestro objetivo actual me ha parecido que es suficiente.

Documento. Descarga [desde este enlace] el código de este script.



martes, 25 de marzo de 2025

Ofimática. Python.

python-docx

Sangrado de párrafos

En esta entrada sobre el uso de funciones de formato en python-docx trataré una cuestión que se me antoja de suficiente interés como para no prescindir de ella en este breve recorrido por esta biblioteca: el sangrado del texto. Este interés viene dado, como en los casos anteriores, por la frecuencia con se emplea en el trabajo con textos, especialmente el sangrado de la primera línea del párrafo. Por motivos de procedimiento dejaré esta cuestión para el final, ya que antes es conveniente explicar cómo se procede para sangrar el párrafo.

Procedemos a sangrar el párrafo (no sólo la primera línea) por la izquierda (también podemos hacerlo por la derecha). Para ello, tras importar las funciones de trabajo en pulgadas from docx.shared import Inches, desarrollamos la siguiente secuencia de acciones:

  • Escribimos el párrafo según el procedimiento común parrafo1 = documento.add_paragraph(txt), que en este caso emplea la variable txt como parámetro en lugar del texto del párrafo.
  • Accedemos a la propiedad general de formato del párrafo formateoIzq = parrafo1.paragraph_format que referenciamos sobre la variable formateoIzq
  • Y sobre esta variable accedemos a la propiedad left_indent a la que damos valor Inches(1.5)

formateoIzq.left_indent = Inches(1.5)

En realidad los pasos 2 y 3 son una forma de evitar la escritura de una instrucción larga, que también es válida,por lo que ...

parrafo1.paragraph_format.left_indent = Inches(1.5)

... es equivalente a las dos anteriores. Y si quisiéramos que el sangrado fuera a la derecha, sería suficiente con sustituir left_indent por right_indent. Así de sencillo.

De modo similar, si en lugar de sangrar todo el párrafo sólo quisiéramos sangrar la primera línea, recurriríamos a la propiedad first_line_indent como alternativa a cualquiera de las dos anteriores. Por aclararlo mejor: el tercer paso del proceso se concretaría como formateoL1.first_line_indent = Inches(1) (por claridad del código en el scritp, sustituyo la variable formateoIzq por formateoL1).

El valor numérico pasado como parámetro a Inches() puede ser positivo (como en este caso) o negativo Inches(-1), lo que modifica el aspecto del párrafo, pero no supone ninguna modificación de la lógica de script.

Documento Este es el enlace al script explicado en la entrada.

Ofimática. Python.

python-docx

Formatos

No soy yo muy partidario de dedicar tiempo a estas cuestiones, teniendo por delante tantas cosas aun por resolver, pero no queda otro remedio que incluir algunas cosas referidas al formato cuando se trata de generar documentos de forma automática. Por ello dedicaré esta entrada a estas cuestiones; las mínimas.

Por empezar por algún lado, y por eso de la frecuencia de uso (y la utilidad), se me ocurre que nos puede interesar aprender a formatear los párrafos centrándolos, justificándolos, o alineándolos a derecha o izquierda. Para ello lo primero que tenemos que hacer es añadir el acceso a un conjunto de funciones mediante la instrucción pertinente (from docx.enum.text import WD_ALIGN_PARAGRAPH) y tomaré un texto suficientemente largo para que se puedan observar los diferentes ajustes. Este texto queda asociado a la función add_paragraph() como parámetro (parrafo1 = documento.add_paragraph(texto1)) y se escribe en el texto sin formato de ningún tipo; pero si a continuación añadimos la instrucción parrafo1.alignment = WD_ALIGN_PARAGRAPH.RIGHT, el párrafo pasa a estar alineado a la derecha, así que sólo tenemos que sustituir la palabra RIGHT por sus alternativas (.LEFT - .RIGHT - .CENTER - .JUSTIFY), para que se justifique de acuerdo a lo ellas.

Esta es, posiblemente, la forma más sencilla de trabajar con formatos en python-docx, ya que otras opciones de formateo del texto exigen mayor complejidad en su codificación. Por ejemplo, algo tan sencillo como escribir en negrita , cursiva o subrayado requieren otro tipo de estrategias basadas en el uso de la función add_run(). Supongamos, por ejemplo, que queremos destacar una palabra dentro de un párrafo escribiéndola en negrita. El procedimiento sería el siguiente:

  • Primero escribimos el texto que precede a la palabra a resaltar como párrafo sin formato

parrafo=documento.add_paragraph('En este segundo párrafo vamos a poner palabras en ')

  • Y después, para escribir la palabra (o las palabras) en negrita asociamos a la variable parrafo la función ad_run() incluyendo como parámetro dicha palabra, seguida del atributo booleano .bold con su valor en True

parrafo.add_run('negrita').bold = True

  • Para cursiva .italic = True y para subrayado .inderline = True, actuaríamos igual; incluso para añadir más texto sin ninguno de estos tres formatos deberemos emplear la función add_run() asociada a la variable parrafo

parrafo.add_run(' y palabras en '

Un ejemplo de complejidad de formateo que yo considero extremo en cuanto a complejidad (por comparación con lo simple que resulta "manualmente") es definir el tamaño y el tipo de fuente, siendo como es una funcionalidad que podemos considerar básica en el uso de un procesador de texto. En este caso...

  • Primero debemos importar un conjunto de funciones específicas .docx

from docx.enum.style import WD_STYLE_TYPE

  • ...requiriendo haber importado previamente el manejo de valores de tamaño de fuente

from docx.shared import Pt

  • Después debemos asociar a la variable styles el manejo de las funciones de estilo asociadas al documento que referenciamos mediante documento, a la que previamente se asoció el objeto creado con Document()

styles = documento.styles

  • Sobre la variable styles llamamos a la función add_style() que recibe dos parámetros: el primero un string con la denominación que vamos a emplear (vg. 'Calibri_12') y un segundo parámetro con referencia al atributo WD_STYLE_TYPE.CHARACTER (todo ello queda asociado a la variable charstyle

charstyle = styles.add_style('Calibri_12', WD_STYLE_TYPE.CHARACTER)

  • Después accedemos al objeto charstyle.font, que a su vez asignamos a la variable obj_font
  • Y sobre ella al atributo de tamaño obj_font.size = Pt(12) y nombre de la fuente obj_font.name = 'Calibri'

Una vez que ya tenemos creado este conjunto de características puedes usarlo para añadirlo como segundo parámetro de la función add_run() asociada a la variable parrafoX para que el texto que se escriba quede formateado según la fuente y el tamaño que definiste antes. Esto se realiza en dos pasos:

  • Se asocia el añadido de la párrafo al documento a una variable parrafo1 = documento.add_paragraph()
  • Y sobre ella se aplica la función add-run() con dos parámetros: el texto a escribir y el atributo style al que se asigna el identificador del formato que se estableció según el procedimiento anterior

parrafo1.add_run(texto1, style = 'Calibri_12'

Este procedimiento es especialmente interesante cuando se va a trabajar con varios estilos de letra (fuente y tamaño), aunque se puede simplificar cuando el modo de proceder va a ser más sencillo, accediendo al atributo fuente del objeto run

run = parrafo1.runs[0] ->font = run.font->font.name = 'Calibri' + font.size = Pt(12)

Todo este código queda en el script que acompaña esta entrada comentado para que no interfiera en el funcionamiento del algoritmo de base.

Documento. No sé si larga pero algo liosa sí que ha sido esta entrada; así que va siendo hora de finalizarla con el enlace al script asociado.

domingo, 23 de marzo de 2025

Ofimática. Python.

python-docx

Tablas y gráficos

Antes de nada decir que empleo el término gráfico como sinónimo de imagen, con independencia de que esta imagen represente un paisaje, una persona o un gráfico estadístico. Dicho esto, en esta entrada trataré sobre estos posibles contenidos de un documento de texto por ser elementos que se pueden necesitar para la elaboración de los documentos que solemos crear como SEO.

Desde esta perspectiva eminentemente práctica, empecemos por incluir gráficos (imágenes) en nuestro documento; para ello, y dando por resueltos los procesos previos de creación del documento y la inclusión de los contenidos ya conocidos por la entrada anterior, para incluir una imagen en un texto usaremos add_picture(), que precisa de un único parámetro, un string con la dirección de la imagen, incluyendo nombre y extensión, si bien cuando la imagen está en el mismo directorio que el script, es suficiente con el nombre y extensión.

Podemos usar dos parámetros complemetarios (o sólo uno) con la indicación dimensional (height y/o width) y el o los valores necesarios para dimensionar la imagen en pulgadas (v.g. width=Inches(1.25)). Así es en nuestro este caso (documento.add_picture('Aprendizaje.jpg', width=Inches(1.25))) y por ese al inicio del script importamos from docx.shared import Inches

Sigamos ahora con el uso de tablas, empezando por los datos que ésta va a contener, los cuales están contenidos en una tupla de tuplas que contiene tantos registros como deseemos y cada uno de ellos tantos elementos como columnas va a tener nuestra tabla:
registros = (('3', 'Entrevistas', 101),('4', 'Reuniones', 42),('5','Evaluaciones', 36))

No es la forma más sencilla de crear una tabla, pero así aprendemos cómo podemos manejar el acceso a sus celdas para introducir los datos. En realidad crear la tabla es mucho más sencillo, siendo suficiente con emplear la función add_table(), que requiere dos parámetros tipo integer: las columnas y las filas (tabla = documento.add_table(rows=1, cols=3). Las complicaciones empiezan cuando decidimos que nuestra tabla tenga encabezado, ya que se requiere una variable encabezado = tabla.rows[0].cells, gracias a la cual podemos acceder a las celdas de primera fila rows[0].cells de la tabla creada. Sobre esta variable iremos identificando cada una de las celdas del encabezado e incluyendo en ellas su texto encabezado[0].text = 'Identificador'.

Para incluir el contenido de nuestros registros en la tabla utilizamos un bucle for id, cont, num in registros:. Básicamente lo que hace este bucle es recorrer nuestra base de datos, elemento por elemento (id, cont, num in registros:) para posicionarlos según lo establecido en row_cells = tabla.add_row().cells.

Obsérvese que esta instrucción row_cells = tabla.add_row().cells es muy parecida, aunque en realidad muy diferente, a la que nos sirvió para crear el encabezado encabezado = tabla.rows[0].cells, por lo que hay que tener cuidado para no confundirlas.

Este uso del bucle puede resultarnos un poco complicado en estos momentos (aun estamos empezando a programar en Python), así que necesitamos conocer otra forma de utilizar una tabla para una función muy común en la composición de un texto: posicionar texto de forma tabular. Para estos casos podemos trabajar con un código más sencillo. Veamos:

  • Creamos la tabla igual que antes tabla2 = documento.add_table(rows = 2, cols = 2)
  • Accedemos a la primera fila celdas0 = tabla2.rows[0].cells con el mismo procedimiento que antes empleamos para crear el encabezado
  • Y posicionamos el contenido textual en las celdas de esa fila celdas0[0].text = "Primera"

Deberemos repetir el procedimiento para cada una de las filas celdas1 = tabla2.rows[1].cells y sus respectivas celdas, ya que por algo es un procedimiento manual, pero estoy seguro que dentro de poco sabremos implementar un bucle para agilizar el procedimiento, no tan elegante y complejo como el presentado antes, pero suficiente para nuestros fines

Documento. Desde este enlace puedes ver y descargar el script que contiene el código de esta entrada.

Ofimatica. Python.

python-docx

Elementos comunes

Gracias a la biblioteca python-docx podemos automatizar la creación de documentos usando Python, lo que no es equivalente a automatizar esos mismos documentos en lo que se refiere a su contenido. Esta diferencia es importante, sobre todo para no generar falsas expectativas. A pesar de esta limitación, no deja de ser un paso importante, aunque deberá complementarse con otros procedimientos para alcanzar el objetivo de la automatización documental.

Lo que explico en estas entrada se limita a los procedimientos más básicos como es la creación del archivo y la escritura de título y contenido textual, incluyendo los listados.

Ya sabemos por una entrada anterior, que para trabajar con python-docx necesitamos haber instalado previamente esta biblioteca pip install python-docx; después, para incorporar las funciones a los script deberemos utilizar instrucciones, especialmente la siguiente, que permite crear documentos de texto from docx import Document. No es la única necesaria, pero para los objetivos de esta entrada sí.

Empezaremos por crear documento = Document() y guardar documento.save('documento.docx') el documento. La primera instrucción al inicio del script y la segunda al final.

Para poner título usamos documento.add_heading('TÍTULO del documento', level = 0). El primer parámetro es el título propiamente dicho y el segundo es una variable numérica level = 0, que indica el nivel jerárquico del título y que puede expresarse directamente como número, por lo que documento.add_heading('TÍTULO del documento', 0) es equivalente a la anterior.

La escritura de párrafos de texto es muy fáci gracias a la función add_paragraph() cuando el contenido no contiene ningún formato específico. El parámetro de dicha función es el texto a introducir, o en su caso, la variable que lo referencia; por ejemplo parrafo1 = documento.add_paragraph('Párrafo simple...). Como ves, se puede implementar directamente, sin utilizar ninguna varaible.

Si necesitamos provocar un salto de página disponemos de la función dd_page_break(), que carece de parámetros, y que va asociada al objeto document() creado previamente documento.add_page_break().

Por último para crear un listado sólo tenemos que añadir el parámetro style a add_paragraph(), resultado documento.add_paragraph('Primer elemento de la lista', style='List Bullet'). Podemos usar style='List Number' como alternativa.

Documento. Este enlace te da acceso al script explicado en esta entrada.

sábado, 22 de marzo de 2025

Ofimática. Python.

python-docx

La biblioteca

Cuando lo que se necesita es automatizar la creación de un documento MS-Word, python-docx es una opción excelente: gratuita, de fácil acceso, sencilla de manejar y muy funcional.

Ya explicaré con más detalle por qué y cómo usar esta biblioteca, así que aquí sólo quiero adelantar lo más básico para poder empezar a utilizarla. Esta biblioteca no viene instalada por defecto en la versión básica de Python, así que tenemos que instalarla desde el CMD mediante la instrucción pip install python-docx.

Para usarla en los script deberemos llamar específicamente a aquellos módulos que deseemos utilizar, siendo el más básico e imprescindible form docx import Document, que permite crear un documento y ejecutar sobre él las principales acciones, incluyendo la de guardarlo.

Otro módulo de interés y uso frecuente es el que permite trabajar con formatos from docx.enum.text import WD_PARAGRAPH_ALIGMENT.

Para finalizar esta breve introducción, te sugiero la lectura de la documentación oficial de la biblioteca de la que te dejo algunos enlaces de interés:

Textos. Python

Ofimática con Python

Dentro de las muchas utilidades que nos ofrece Python, las más próximas a los objetivos nucleares de este blog son las que tienen que ver con la ofimática. Aunque existen diferentes enfoques, mi opción actual tiene que ver con la creación automatizada de los principales documentos ofimáticos en términos de uso por parte de los SEO: documentos de texto, hojas de cálculo y presentaciones.



Aunque LibreOffice (LO) presenta la opción de crear y trabajar con script Python "internos", esto es, script con igual funcionalidad que los creados en OOo Basic, hasta donde yo sé, Python no cuenta con bibliotecas que permitan crear documentos LO "externamente", esto es, sin que sea necesario trabajar desde un servicio de la suite. Microsoft Office (MSA) sí, lo que constituye una ventaja a su favor. No obstante es factible crear documentos MSA y seguir trabajando con la suite LO, ya que ésta accede sin dificultad a los documentos creados para la primera.

Dentro de los productos ofimáticos, los de mayor uso (y funcionalidad) para los SEO son los documentos de texto, seguidos a mucha distancia y escasa entre ellos, por las hojas de cálculo y las presentaciones (tipo Power Point). Python cuenta con bibliotecas específicamente desarrolladas para automatizar la creación de estos documentos en soporte MSA.

Podemos automatizar la creación de documentos de texto (Word - Writer) mediante las utilidades que ofrece la biblioteca python-docx, crear hojas de cálculo (Excell - Calc) mediante openpyxl y generar automáticamente presentaciones mediante python-pptx (Power Point - Impress)
 

jueves, 23 de mayo de 2024

Textos. Python

Documentos específicos mediante Python


Python nos ofrece también la posibilidad de crear mediante script documentos genuinamente específicos de servicios ofimáticos. Esto es posible mediante el uso de bibliotecas de módulos o paquetes de funciones que previamente deben ser instalados e importados. Me refiero concretamente a la creación desde script Python de documentos Word, Excel o Acrobat. Los dos primeros son accesibles desde LibreOffice gracias a la compatibilidad que esta suite mantiene con MSO, por lo que crear un documento Word o Excel es equivalente a crear un documento accesible desde LO-Writer y LO-Calc respectivamente. Y lo es en sentido pleno: LO puede trabajar con esos documentos como si fueran propios.


Así que mantengo como icono de esta entrada el mismo que en las anteriores referidas al uso de Python como herramienta para el trabajo con recursos ofimáticos, aunque en este caso una representación más adecuada del planteamiento de trabajo sería la siguiente:

Ésta no es una representación totalmente satisfactoria del modo 1A2, ya que no lo es al completo, al faltar formatos como .pdf, pero también otros que no explicité en el esquema inicial (como .cvs, por ejemplo), ni lo es en sentido estricto, ya que en esta entrada me voy a limitar a exponer cómo desarrollar la rama superior de esta representación (documento de texto), dejando para una posterior entrada el trabajo sobre  la rama inferior (hoja de cálculo) Esta división, que facilita la coherencia con otra documentación, está justificada por la complejidad de la temática que aborda, que exige realizar cuanto menos un primer acercamiento a temas como el uso de bibliotecas externar en Python, incluyendo su instalación y uso dentro de los script a crear. También parece una exigencia lógica que deriva de la complejidad de la propia biblioteca que facilita a Python el trabajo con documentos Word.

En efecto, para desarrollar esta línea de trabajo (1A2) es necesario, en primer lugar, entender que Python, tal y como se instala en origen ofrece nada más que la punta de un iceberg de posibilidades, siendo posible (y necesario) implementar bibliotecas, paquetes de funciones o módulos, nuevas clases... (a gusto del consumidor) para desarrollar procesos específicos de trabajo, como es el caso de la creación de soportes ofimáticos (en este caso documentos MSO-Word).

Este proceso tiene cierta complejidad, aunque esta suficientemente simplificado gracias a los recursos disponibles en Python. A mí me ha servido parta entenderlo, además de la lectura de documentación, la explicación que proporciona Errodinger en este vídeo. Considero que es una buena síntesis de las opciones disponibles y de cómo proceder. No es el único recurso disponible, pero sirve para empezar.



En síntesis, la opción básica de instalación es el comando pip install NombreLibreria aplicado desde el cmd, tal y como te muestro en este vídeo que grabé mientras instalaba la librería para trabajar con documentos .docx. Para su visualización recomiendo la opción Pantalla completa


La instrucción completa es pip install python-docx, a escribir tras la cadena que genera Símbolo del sistema (consola o cmd) C:\Users\NombreUsiario>

Una vez instalada la librería python-docx, existen dos formas de saber si forma parte de las librerías instaladas, podemos utilizar indistintamente pip freeze o pip list (ambas desde Símbolo de sistema,cmd o consola) que listan todas las librerías, paquetes (de módulos) o clases instaladas y su versión (1)

Además, y ahora desde el IDE, podemos comprobar si un paquete está correctamente instalado (esto es, si funciona) utilizando la instrucción import NombreLibreria: si funciona correctamente no obtendremos ninguna respuesta (el intérprete queda a la espera), pero si no funciona obtendremos un mensaje de error. Por ejemplo, import doc devuelve...

Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    import doc
ModuleNotFoundError: No module named 'doc'

        Otra instrucción que nos interesa conocer, también a aplicar desde IDE, es dir(NombreLibreria), que nos devuelve el conjunto de funciones o métodos de que dispone la librería en cuestión. Por ejemplo, dir(docx) devuelve...

        ['Document', 'ImagePart', 'RT', 'TYPE_CHECKING', 'Type', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'annotations', 'api', 'blkcntnr', 'dml', 'document', 'drawing', 'enum', 'exceptions', 'image', 'opc', 'oxml', 'package', 'parts', 'section', 'settings', 'shape', 'shared', 'styles', 'table', 'text', 'types']

        Saber cuáles son los componentes de la librería es útil como síntesis, pero no  es suficiente cuanto estamos iniciando el aprendizaje del funcionamiento de un paquete, así es conveniente disponer de información más detallada, sobre componentes, funcionamiento y ejemplos. Para ello es recomendable consultar los siguientes enlaces (2):

        Pasemos ahora a la crear un documento Word. Únicamente es una ejemplificación de esta opción para constatar su funcionalidad tal y como se representa en este esquema del modo externo (modo 1)


        El script ha sido creado desde el IDE estándar de Python y modificado desde bloc de notas. Con esto quiero significar que, al menos para elaborar recursos básicos no es necesario trabajar con IDE complejos ni especializados. Este es el script...

        from docx import Document #-> Acceso a la librería docx para importar el método Document

        document = Document() # -> Creamos un documento mediante la función Document()

        # Escritura del encabezado del documento mediante la función add_heading()

        document.add_heading('Escritura de texto en formato Word. Modo 1A2')

        # Siguen a continuación tres modos de escribir contenido (párrafos)

        paragraph = document.add_paragraph('Primer párrafo: Texto escrito directamente mediante asignación de contenido a variable.')

        p = document.add_paragraph('Segundo párrafo: Texto plano seguido de texto en ')
        p.add_run('negrita').bold = True
        p.add_run(' y texto en ')
        p.add_run('itálica.').italic = True

        texto_parrafo3=input("Escribe el tercer párrafo")
        paragraph = document.add_paragraph(texto_parrafo3)

        # Finalizamos  guardando el documento creado mediante la función save()

        document.save('textopruebaword.docx')

        ... y este el resultado (captura de pantalla del documento Word). Te recomiendo que descargues el script Python, analices y repliques el proceso. Aunque no es obligatorio, te sugiero que guardes el script en la unidad D:
         

        Con lo anterior he desarrollado el proceso representado en el esquema precedente hasta el punto en que se crea un documento Word. Aquí puede finalizar el proceso, pero también podemos continuarlo accediendo al documento desde LO-Writer.


        Efectivamentea diferencia de los modos 1A1 y 2B, en 1A2 el uso de LibreOffice no es necesario salvo como recurso para visualizar el contenido del documento y como alternativa a MSO-Word, dada la posibilidad que ofrece LO por su compatibilidad con las extensiones MSO. 

        También es posible implementar macros OOo Basic, pero a diferencia del modo 1A1, considero que esta opción sólo se debe contemplar de forma excepcional, ya que deberíamos ser capaces de generar un documento final mediante Python.

        NOTAS

        (1) Eso en Windows; para Linux y Mac ver enlace
        (2) Conforme vayamos incorporando otras bibliotecas iré ampliando el listado de enlaces a la documentación de las mismas.