es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo copiar y pegar una tabla desde las aplicaciones de Office a la interfaz gráfica de PyQt5

Soy nuevo en Python 3 y PyQt5 y me enfrenté a un problema que me dejó atónito por su crueldad. Asumo que mi falta de experiencia en Python es la causa raíz, pero aún así…

Tarea

La tarea consiste en copiar una parte de una tabla de aplicaciones de oficina (MS Office, LibreOffice) y pegarla en una GUI implementada por PyQt5 y QTableView. Estudié algunos ejemplos simples en Internet, pero mi tarea es un poco más complicada. Es necesario copiar algunos datos que están rodeados por celdas vacías en todos los lados:

Imagen

Lo que se ha intentado

Ctrl+C, Ctrl+V no funcionan, así que probé formas más complicadas que se enumeran a continuación.

QtWidgets.QApplication.clipboard().text()

Comencé con lo más sencillo: el contenido de texto de QtWidgets.QApplication.clipboard() y encontré un caos completo que depende de la fuente de una tabla:

   MS Office Excel: '\t\t\n\t1\t2\n\t3\t4\n'
  LibreOffice Calc: '1\t2\n3\t4\n'
    MS Office Word: '\t\t\t\n\t1\t2\t\n\t3\t4\t\n\t\t\t\n'
LibreOffice Writer: '\n\n\n\n\n1\n2\n\n\n3\n4\n\n\n\n\n\n'

Como podemos ver, en muchos casos se pierde información sobre las celdas vacías.

QtWidgets.QApplication.clipboard().text(“html”)

Obtener el contenido HTML del portapapeles es una solución, pero de una manera muy torpe. El texto con etiquetas HTML tenía que ser cuidadosamente limpiado de cualquier etiqueta de basura, que MS Word produce especialmente en gran cantidad. Es largo y complicado, sin la certeza de que con una actualización de Office no tenga que reescribir el algoritmo.

pandas.read_clipboard(header=None)

Pensé que pandas podría ayudarme, pero descubrí que la salida de pandas.read_clipboard(header=None) era casi absurda en comparación con la salida de QtWidgets.QApplication.clipboard().text():

MS Excel:
           2
0   1<br>
NaN NaN  NaN
    1.0  2.0
    3.0  4.0

LibreOffice Calc:
   0  1
0  1  2
1  3  4

MS Word:
              3
0   1   2<br>
NaN NaN NaN NaN
    1.0 2.0 NaN
    3.0 4.0 NaN
    NaN NaN NaN

LibreOffice Writer:
   0
0  1
1  2
2  3
3  4

Agradecería si me dieran algún consejo sobre cómo resolver este problema de la manera más óptima.

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Solución para copiar una tabla formateada desde MS Office y LibreOffice a PyQt5 QTableView

    Copiar tablas formateadas desde MS Office y LibreOffice a un PyQt5 QTableView puede ser una tarea desafiante. La causa raíz de este problema es que los datos están rodeados de celdas vacías en todos los lados, y el portapapeles pierde la información sobre las celdas vacías en muchos casos. Por lo tanto, Ctrl+C, Ctrl+V no funciona, y otros métodos como QtWidgets.QApplication.clipboard().text() o QtWidgets.QApplication.clipboard().text("html") no proporcionan una solución limpia.

    Una solución posible es usar pandas para leer el contenido del portapapeles utilizando el siguiente código:

    import pandas as pd
    clipboard = pd.read_clipboard(header=None)
    model = PandasModel(clipboard) # PandasModel es una implementación personalizada (explicada a continuación)
    tableView.setModel(model)
    

    Sin embargo, esta solución puede no funcionar como se espera debido a la misma razón explicada anteriormente. La salida de pd.read_clipboard(header=None) puede no coincidir con nuestras expectativas y podemos obtener una salida absurda.

    Por lo tanto, la forma óptima de resolver este problema es crear una implementación personalizada para analizar el contenido del portapapeles y extraer los datos de la tabla formateada. Se pueden seguir los siguientes pasos para implementar una solución personalizada:

    1. Leer el contenido del portapapeles utilizando QtWidgets.QApplication.clipboard().mimeData().
    2. Analizar el contenido del portapapeles para extraer los datos de la tabla formateada.
    3. Utilizar los datos de la tabla analizada para poblar un QStandardItemModel.
    4. Establecer el QStandardItemModel como el modelo para el QTableView.

    A continuación se muestra un ejemplo de implementación de una función parseTable() que se puede utilizar para analizar el contenido del portapapeles:

    import re
    
    def parseTable(data):
        table = []
        lines = data.splitlines()
        n_lines = len(lines)
        empty_line = [""] * len(lines[0].split("\t")) # para manejar celdas vacías en todos los lados
        for i in range(n_lines):
            if not lines[i]: # excluir líneas vacías
                continue
            if i > 0 and i < n_lines-1: # excluir la primera y última línea
                cells = lines[i].split("\t")
                if cells == empty_line: # excluir si todas las celdas están vacías
                    continue
                cells = list(map(lambda cell: re.sub(r"<[^>]*>", "", cell).strip(), cells)) # eliminar etiquetas html
                table.append(cells)
        return table
    

    Esta función parseTable() puede manejar los datos de la tabla de MS Office y LibreOffice excluyendo las celdas vacías en todos los lados y eliminando las etiquetas html.

    El siguiente paso es poblar un QStandardItemModel utilizando los datos de la tabla analizada. Aquí se puede utilizar una implementación personalizada PandasModel, que hereda de QAbstractTableModel y se puede utilizar para cargar un DataFrame de pandas en un QTableView. A continuación se muestra un ejemplo de implementación de una clase PandasModel:

    from PyQt5.QtCore import Qt, QModelIndex, QVariant, QAbstractTableModel
    
    class PandasModel(QAbstractTableModel):
    
        def __init__(self, data, parent=None):
    
            QAbstractTableModel.__init__(self, parent)
            self._data = data
    
        def rowCount(self, parent=QModelIndex()):
            return len(self._data.values)
    
        def columnCount(self, parent=QModelIndex()):
            return self._data.columns.size
    
        def data(self, index, role=Qt.DisplayRole):
            if index.isValid():
                if role == Qt.DisplayRole:
                    return str(self._data.values[index.row()][index.column()])
            return QVariant()
    

    Finalmente, a continuación se muestra un ejemplo de implementación de cómo utilizar las funciones anteriores para cargar los datos de la tabla formateada en un QTableView.

    from PyQt5.QtWidgets import QApplication, QTableView, QWidget, QVBoxLayout
    from PyQt5.QtGui import QStandardItem, QStandardItemModel
    import re
    
    class Window(QWidget):
    
        def __init__(self):
    
            QWidget.__init__(self)
    
            table = QTableView()
            vbox = QVBoxLayout()
            vbox.addWidget(table)
            self.setLayout(vbox)
    
            clipboard = QApplication.clipboard()
            mime = clipboard.mimeData()
            if mime.hasHtml():
                html = mime.html()
                table_data = parseTable(html)
                model = PandasModel(table_data)
                table.setModel(model)
    
    
    if __name__ == "__main__":
    
        app = QApplication([])
        win = Window()
        win.show()
        app.exec_()
    

    Esta implementación se puede utilizar para copiar tablas formateadas desde MS Office y LibreOffice a un PyQt5 QTableView de manera óptima.

Comments are closed.