PyUNO: Macros y componentes para OpenOffice/LibreOffice con Python
Contenido
- 1 Documentación
- 2 Documentación y recursos generales
- 3 Reflexión
- 4 Documentación
- 5 Ejemplos del api de calc
- 6 Documentación
- 7 Añadiendo fórmulas a Calc
- 8 Documentación
- 9 Ejecutando un job
- 10 Un diálogo para insertar texto
Documentación
|
Instalación de Libreoffice (Ubuntu)
- Basta con tener instalado libreoffice si solo se van a hacer macros.
- Si se van a crear componentes instalar adicionalmente el paquete libreoffice-dev
$ apt-get install libreoffice-dev
- Vamos a utilizar libreoffice 3.5, que usa python 2.7
Introducción al API de Openoffice: conceptos
Interfaces
- Un conjunto de métodos y/o atributos que exponen funcionalidad. Aspectos de la implementación de un objeto.
- En el api de UNO, todos los nombres de interfaces empiezan con X
- Todos los interfaces extienden XInterface
Servicios
- Servicios "New-style": Especifica que objetos que implementan un determinado interfaz (ej: com.sun.star.bridge.XUnoUrlResolver), estarán disponibles bajo un determinado nombre (ej: com.sun.star.bridge.UnoUrlResolver) en el service manager del component context (ver más adelante). Para que un objeto que implementa un servicio pueda implementar varios aspectos o interfaces, el interfaz que implementa heredará de varios otros interfaces.
- Servicios "Old-style": Los servicios oldstyle pueden verse como un conjunto de interfaces y/o propiedades.
- Pueden o no exponerse a través del service manager, servir como base de otros servicios (en el new-style, el mecanismo preferido para proporcionar servicios base es implementar interfaces que heredan de varios interfaces), o simplemente servir para agrupar un conjunto de propiedades.
- Pueden implementar interfaces opcionales
- Pueden incluir otros servicios, lo que significa que expondrán el conjunto de sus interfaces y de los otros servicios.
Obtener un servicio a través del serviceManager:
desktop = serviceManager.createInstanceWithContext("com.sun.star.frame.Desktop", context)
Más adelante se hablará del serviceManager y del component context
Obtener los nombres de servicio que soporta un objeto
obj.getSupportedServiceNames()
Debido a la existencia de servicios old-style, hay que tener cuidado a la hora de interpretar el api: Algunos servicios no pueden ser instanciados directamente con el service manager
|
Propiedades
- Pares de nombre-valor que expone un servicio.
- Normalmente suelen ser utilizados para atributos no estructurales (ej: color, tamaño, pero no objetos padre o hijos)
- Se suelen acceder mediante com.sun.star.beans.XPropertySet, pero también en algunos casos, a través de com.sun.star.beans.XPropertyAccess ó com.sun.star.beans.XMultiPropertySet
Obtener propiedad (XPropertySet):
sheet.getPropertyValue("IsVisible")
Establecer propiedad:
sheet.setPropertyValue("IsVisible", False)
Obtener propiedades de un objeto:
propsinfo = sheet.getPropertySetInfo() props = propsinfo.getProperties() for prop in props: print(prop)
Singletons
- Implementación de interface del que solo existe una instancia accesible desde el component context.
Componentes / extensiones
- Son librerías que contienen implementaciones de uno o varios servicios en cualquiera de los lenguajes que soporta UNO
Estructuras, Constantes, y Enumeraciones
- Estructuras: Conjunto de miembros, similar a la estructuras en C. Soportan herencia simple.
- Instanciando una estructura:
import uno uno.createUnoStruct("com.sun.star.beans.PropertyValue")
- Constants: tipo que agrupa varios valores constantes
- Obteniendo el valor de una constante
import uno uno.getConstantByName("com.sun.star.sheet.ConditionOperator.GREATER")
- Enum: Similar a una enumeración en c++
Módulos
- Espacios de nombres, similares a los namespaces de C++ o a los paquetes en Java. Agrupan servicios, interfaces, structs...
ComponentContext
- Objeto con el que se obtene el singleton de ServiceManager
- Puede obtenerse dependiendo de si el código a ejecutar va a ser una macro o un componente.
ServiceManager
- Objeto con el que se instancian servicios
serviceManager = ctx.ServiceManager
Macros
Son pequeños programas que usan el api UNO para automatizar tareas en documentos
Ejemplo:
import uno def holaMundoCalc(): # Accedemos al modelo del documento actual model = XSCRIPTCONTEXT.getDocument() # Accedemos a la primer hoja del documento hoja = model.getSheets().getByIndex(0) # Accedemos a la celda A1 de la hoja celda = hoja.getCellRangeByName("A1") # Escribimos en la celda celda.setString("Hola Mundo en Python") return None
- XSCRIPTCONTEXT: variable global en el script que contiene Document (el objeto que representa un documento), Desktop y ComponentContext
- Por defecto todas las funciones del script se exportan como macros. Para limitarlos:
g_exportedScripts = (holaMundoCalc, ) # solo expondrá holaMundoCalc, aunque haya otras funciones
- El comentario de una función se muestra como descripción de la macro en el diálogo de macros
Ejecución de macros
Puede hacerse a través del díalogo tools -> macros -> run macro... o a través de tools -> macros -> organize macros -> python...
Distribución de macros
Existen varios modos de distribuir una macro
- En directorio de usuario:
- ~/.openoffice.org/3/:user/Scripts/python
- ~/.config/libreoffice/3/user/Scripts/python
- En directorio compartido (para todos los usuarios)
- /usr/lib/libreoffice/share/Scripts/python
- /usr/lib/openoffice/share/Scripts/python
- Embebido en documentos:
- Descomprimir documento con unzip
- Incluir el script en cualquier ruta bajo el directorio Scripts
- Modificar META-INF/manifest.mf, incluyendo referencia al script:
<manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/python/mostrarversion.py"/>
- Volver a comprimir el documento
- Empaquetado
- Se creará una estructura de archivos:
/description.xml /Scripts/python/holamundo.py /META-INF/manifest.xml
- Donde description.xml sirve para dar información sobre el paquete (ver 2_simplemacropkg), y manifest.mf referencia la ruta base de los scripts del siguiente modo:
<manifest:manifest> <manifest:file-entry manifest:media-type="application/vnd.sun.star.framework-script" manifest:full-path="Scripts"/> </manifest:manifest>
Transformando código Java a Python: diferencias
Una buena forma de aprender sobre UNO es mediante los ejemplos que hay disponibles. Lamentablemente la mayoría de código es Java o C++.
- La primera diferencia es obvia: python no es estáticamente tipado
- Para obtener y usar un servicio, no es necesario instanciar el servicio y luego hacer un UnoRuntime.queryInterface para obtener el interface deseado del tipo correcto. Basta con instanciar el servicio o componente, y usarlo directamente. Ej:
// doc es un objeto documento XSpreadsheetDocument xSpreadsheetDocument = (XSpreadsheetDocument) UnoRuntime.queryInterface(XSpreadsheetDocument.class, doc); XSpreadsheets xSpreadsheets = xSpreadsheetDocument.getSheets(); xSpreadsheets.insertNewByName("MySheet", (short)0);
Esto se traduce a:
xSpreadsheets = doc.getSheets() xSpreadsheets.insertNewByName("MySheet", 0)
- Los arrays y listas se transforman de y hacia python como tuplas, NO como listas!.
com.sun.star.beans.PropertyValue[] conditions = new com.sun.star.beans.PropertyValue[1]; conditions[0] = condition1 conditions[1] = condition2 obj.metodoquerecibeprops(conditions)
- Lo anterior en python sería:
conditions = (condition1, condition2) obj.metodoquerecibeprops(conditions)
Conectando con libreoffice
Podemos ejecutar scripts python para que se conecten a libreoffice.
- Arrancar libreoffice escuchando en un puerto
$ soffice --norestore "-accept=socket,host=localhost,port=2002;urp;"
- Conectarse a libreoffice y obtener objetos necesarios
localContext = uno.getComponentContext() resolver = localContext.ServiceManager.createInstanceWithContext( "com.sun.star.bridge.UnoUrlResolver", localContext ) ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" ) smgr = ctx.ServiceManager desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx) model = desktop.getCurrentComponent() text = model.Text cursor = text.createTextCursor() text.insertString( cursor, "Hello World", 0 )
Calc: Api
- La mayor parte del api que nos interesa está bajo:
- com.sun.star.sheet
- com.sun.star.table
- Filtros de exportación / Importacion
Componentes y extensiones en PyUNO
- Se encuentra en: /usr/lib/libreoffice/program/unopkg
http://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Writing_UNO_Components http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/ http://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Writing_the_Specification -> especificación de UNO IDL
|
- Los componentes pueden crearse en python extendiendo de unohelper.Base, e implementando los interfaces necesarios.
- Pueden implementar interfaces ya existentes en el api de UNO, o crear nuevos
- Los nuevos interfaces y servicios se declaran mediante UNOIDL en un fichero .idl
Herramienta unopkg
Es la herramienta con la que se pueden instalar, listar, y desinstalar paquetes
- Instalar extensión
$ unopkg add myextension.oxt
- Instalar extensión para todos los usuarios
$ unopkg add --shared myextension.oxt
- Listar las extensiones instaladas
$ unopkg list
- Eliminar una extensión
$ unopkg remove <identifier>
Creación de extensión que exponga algún servicio/interfaz
- Compilar .idl a .urd:
/usr/lib/libreoffice/sdk/bin/idlc -w -I /usr/lib/libreoffice/sdk/idl XMyComp.idl
- meter .urd en .rdb con regmerge
/usr/lib/ure/bin/regmerge XMyComp.rdb /UCR XMyComp.urd
- Implementación del servicio/interfaz en python
- Crear description.xml
- crear .xcu
- crear META-INF/MANIFEST.MF
- Empaquetar todo en zip, nombrandolo como myextension.oxt
- Instalar con unopkg , ó abriendo la extensión con libreoffice.
/usr/lib/libreoffice/program/unopkg add myextension.oxt
Interacción con componentes
Jobs
- Componentes que pueden ser ejecutados como respuesta a un evento en el sistema, o también directamente
- Han de implementar com.sun.star.task.XJob
- Son ejecutados por el servicio com.sun.star.task.JobExecutor
- Pueden ejecutarse mediante eventos estandar del sistema, o mediante eventos personalizados
- Lista de eventos estándar del sistema
http://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Jobs/List_of_Supported_Events
Dialogos
Se pueden crear de dos modos:
- Directamente con el api de com.sun.star.awt
- Usando diálogos creados con el IDE básico de dialogos de openoffice, e instanciandolos desde el componente o macro python:
args = (doc,) dialogprov = serviceManager.createInstanceWithArgumentsAndContext("com.sun.star.awt.DialogProvider2", args, ctx) #dialog = dialogprov.createDialogWithHandler("file:///tmp/NameDialog.xdl", Handler()) # También se puede referenciar el archivo del diálogo dialog = dialogprov.createDialogWithHandler("vnd.sun.star.script:NeoLibrary.NameDialog?location=application", Handler()) dialog.execute()
Tips
Aumentar nivel de logs
- Modificar:
/usr/lib/libreoffice/share/extensions/script-provider-for-python/pythonscript.py
LogLevel.use = LogLevel.NONE # alternavly use LogLevel.ERROR or LogLevel.DEBUG LOG_STDOUT = False # True, writes to stdout # False, writes to user/Scripts/python/log.txt
Obtener ruta de un paquete desplegado
- útil para acceder a ficheros de la extensión desplegada, ver ejemplo de toolpanel
pip = self.ctx.getValueByName("/singletons/com.sun.star.deployment.PackageInformationProvider" ) s = pip.getPackageLocation(extensionID) # s es una string con la ruta absoluta al paquete