Tablas complejas (.odt) (III)
Acceso a tablas
Sirva lo visto en la entrada anterior como base para un mejor conocimiento de la estructura de tablas de un documento .odt y ocupémonos en esta de cómo acceder a esa colección y a cada una de esas tablas mediante código Python.
Para manejar documentos .odt desde Python disponemos de varias bibliotecas que ahora únicamente menciono para que puedas conocerlas y estudiarlas con más detalle. Me refiero a:
- ODFPY, biblioteca de bajo nivel que permite controlar prácticamente cualquier aspecto de un archivo .odt
- EzODF, cuyo objetivo es que crear un documento sea mucho más intuitivo.
- Pypandoc, pensada para la conversión de archivos .odt (de Markdown a ODT o de ODT a PDF/HTML).
- y finalmente Relatorio, interesante para trabajar con plantillas.
Dado que nosotros nos centramos ahora en la extracción de datos, las bibliotecas que usaremos serán preferentemente las dos primeras, por lo que te sugiero que te centres en ellas en tus investigaciones.
El acceso al contenido de un documento .odt es, en realidad, el acceso a un archivo zip que contiene varios documentos XML, por lo que si conviertes un archivo .odt a .zip y accedes a su contenido podrás localizar, entre otros, un documento llamado content.xml; si lo abres verás todo el código de etiquetas xml entre las que están las que identifican a las tablas y su estructura.
Pero si lo que deseas es acceder a las tablas de forma mucho más funcional puedes aplicar este script al documento que tu elijas, siempre que sea un documento.odt y contega tablas.
from odf import opendocument, table
#Funciones de los módulos de la biblioteca odfpy
'''
1. opendocument. Permite la gestión del archivo como un "contenedor" completo
Cargar y guardar: Permite abrir archivos (opendocument.load()) y salvar los cambios (doc.save())
Crear documentos: Generar un documento nuevo desde cero
Acceso a la estructura: Administrador de todos los elementos del archivo (tablas, párrafos, estilos)
2. table. Trabajo con las hojas de cálculo y las tablas de los documentos de texto
Creación de tablas: Permite definir nuevas tablas (table.Table)
Gestión de filas y celdas: Proporciona los objetos necesarios: table.TableRow para las filas y table.TableCell para las celdas
Atributos de celda: Manejar propiedades como la unión de celdas (spans) y el nombre de la tabla
'''
doc = opendocument.load("mi_documento.odt") # Cargar archivo
tablas = doc.getElementsByType(table.Table) # Buscar todas las tablas (etiqueta )
for t in tablas: # Recorrido y visualización del identificador o nombre de las tablas
nombre_tabla = t.getAttribute("name")
print(f"Encontrada: {nombre_tabla}")
Observa que estamos trabajando con la biblioteca ODFPY, concretamente los módulos opendocument, que permite acceder al documento (doc = opendocument.load("mi_documento.odt"), y table, que facilita el acceso a las tablas (tablas = doc.getElementsByType(table.Table)); de las cuales visualizamos su atributo "name" (nombre_tabla = t.getAttribute("name")), que es el mismo que nos permite ver el navegador del documento.
Hasta aquí hemos conseguido acceder al equivalente de visualización del listado de tablas del documento que proporciona la utilidad Navegador del procesador de texto (Writer), lo cual no deja de tener su interés, ya que refuerza la idea de que es útil conocer y manejar ese navegador. Pero la utilidad inmediata puede que aun no se constate, así que vamos a avanzar un poco más en esa línea, accediendo al contenido de una celda concreta de una tabla determinada.
Aquí nos interesa hablar de una cuestión que puede parecer obvia, pero que no deja de tener relevancia en el manejo de estas tablas-formulario: en ellas no existe nada que diferencie lo que en la lógica de la estructura de una tabla de datos (base de datos) se puede considerar una diferenciación fundamental y básica: no es lo mismo una etiqueta o nombre de campo que el campo (contenido del campo) en si mismo. En una tabla-formulario una determinada posición o celda (A1, por ejemplo) simplemente contiene texto (y siempre texto) o no lo contiene, sin importar si ese texto es el dato o es la etiqueta. El conocimiento de esa diferencia, con absoluta seguridad, sólo lo puede aportar la persona que trabaja con el documento.Pueden emplearse determinadas estrategias para inferir si estamos ante uno u otro contenido (ejemplo, si el texto de la celda finaliza en : (dos puntos), es de esperar que se trate de una etiqueta), pero no existe seguridad absoluta en que la inferencia sea viable ni cierta. De aquí quiero derivar la importancia de la implicación directa del programador en el diseño del procedimiento, por encima de la automatización y/o del uso de la IA.
Pero volvamos a retomar el desarrollo de código para acceder al contenido de las tablas, ahora con independencia de la naturaleza de este contenido, el cual es "etiquetado" como etiqueta o como dato por el programador. El código sólo puede facilitar la automatización del acceso, lo cual no es es poco.
from odf import opendocument, table, teletype, text
#Funciones de los módulos nuevos de la biblioteca odfpy
'''
3. text. Facilita el trabajo con los elementos estructurales del documento
Párrafos (text.P): Crear los bloques de texto que se insertan en celdas o en el cuerpo del documento
Estilos de texto: Definir encabezados (text.H) y aplicar formatos a fragmentos de texto
4. teletype. Utilidad para facilitar el trabajo con los nodos XML
extractText(): Extrae todo el texto plano de un elemento (una celda o una tabla completa), ignorando las etiquetas internas. Necesario para leer contenido
addTextToElement(): Inserta una cadena de texto dentro de un elemento ODF, encargándose de convertir los caracteres originales
'''
# --- 1. CONFIGURACIÓN ---
ruta_archivo = r"" #Aquí la ruta completa de tu documento
nombre_tabla_objetivo = "" #Aquí la tabla elegida según nombre de el navegador
fila_destino = 0 # Fila de interés
columna_destino = 0 # Columna de interés
nuevo_texto = "" #Aquí tu nuevo texto
# --- 2. ACCESO ----
doc = opendocument.load(ruta_archivo) #Acceso al documento
# --- 3. LOCALIZACIÓN E INVENTARIO ---
tablas = doc.getElementsByType(table.Table)
tabla_encontrada = None
print("\n--- Inventario de tablas en el documento ---")
for i, t in enumerate(tablas):
nombre_actual = t.getAttribute("name")
if nombre_actual == nombre_tabla_objetivo: # Verificamos si es la tabla que buscamos para añadir una marca visual
tabla_encontrada = t
print(f" {i+1}. [X] {nombre_actual} <-- ¡TABLA IDENTIFICADA!") # Marca visual (-->) y mayúsculas para resaltara
else:
print(f" {i+1}. [ ] {nombre_actual}") # Listamos el resto de tablas con un prefijo normal
print("-------------------------------------------\n")
if not tabla_encontrada: # Si tras el bucle no se encontró, detenemos el proceso con un aviso
print(f"Error: No se encontró la tabla '{nombre_tabla_objetivo}' en el listado anterior.")
exit() # Aquí ponemos un exit()
# --- 4. ACCESO Y ESCRITURA CORREGIDA ---
if tabla_encontrada:
filas = tabla_encontrada.getElementsByType(table.TableRow)
if fila_destino < len(filas):
celdas = filas[fila_destino].getElementsByType(table.TableCell)
if columna_destino < len(celdas):
celda_objetivo = celdas[columna_destino]
contenido_viejo = teletype.extractText(celda_objetivo) # LEER: Esto sigue funcionando igual
print(f"Contenido previo: '{contenido_viejo}'")
# ESCRIBIR: El procedimiento correcto
celda_objetivo.childNodes = [] # 1. Limpiamos la celda de párrafos u objetos anteriores
nuevo_parrafo = text.P() # 2. Creamos un elemento párrafo
teletype.addTextToElement(nuevo_parrafo, nuevo_texto) # 3. Añadimos el texto al párrafo
celda_objetivo.addElement(nuevo_parrafo) # 4. Insertamos el párrafo dentro de la celda
# --- 5. GUARDADO ---
doc.save(ruta_archivo)
print(f"Éxito: Se ha escrito '{nuevo_texto}' en la celda.")
else:
print("Error: Columna no encontrada.")
else:
print("Error: Fila no encontrada.")
else:
print(f"No se encontró la tabla '{nombre_tabla_objetivo}'.")
Este script, además de acceder y visualizar a las tablas, identifica y se centra en una de ellas (nombre_tabla_objetivo = "") que queda identificada en el listado (if nombre_actual == nombre_tabla_objetivo:). Sobre ella realizamos dos acciones:
- Capturar el texto que contiene la celda
- Escribir en ella, aunque de forma indirecta, pasando antes por el objeto párrafo
Sobre esta base es posible escribir contenido en las tablas, pero también (y es lo que nos interesa ahora) obtener el escrito en documentos trabajados manualmente. De ahí podemos derivar procedimientos de acceso a datos específicos, habiendo previamente dlimitado los documentos a analizar, la tabla según se muestra en el navegador y la celda identificada según vimos en la entrada correspondiente. Todo esto nos permite construir una utilidad de obtención de datos concretos de un documento o de una colección de documentos iguales.
No hay comentarios:
Publicar un comentario
Comenta esta entrada