PyUNO: Macros y componentes para OpenOffice/LibreOffice con Python

De WikiEducator
Saltar a: navegación, buscar



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()



Icon reflection.gif

Reflexión

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

  • Porque son conjuntos de propiedades
  • Porque solo sirven como servicios base de otros servicios
  • O necesitan un tratamiento especial. Ej: com.sun.star.text.TextDocument no puede instanciarse directamente, se obtiene a través de loadComponentFromUrl() de com.sun.star.frame.XComponentLoader




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:
  1. Descomprimir documento con unzip
  2. Incluir el script en cualquier ruta bajo el directorio Scripts
  3. Modificar META-INF/manifest.mf, incluyendo referencia al script:
    <manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/python/mostrarversion.py"/>
  4. 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



Icon casestudy.gif

Ejemplos del api de calc

Leer y ejecutar paso a paso la macro del ejemplo 5_calc_api.



Filtros de exportación / Importacion


Componentes y extensiones en PyUNO

  • Se encuentra en: /usr/lib/libreoffice/program/unopkg




  • 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

  1. Compilar .idl a .urd:
    /usr/lib/libreoffice/sdk/bin/idlc -w -I /usr/lib/libreoffice/sdk/idl XMyComp.idl
  2. meter .urd en .rdb con regmerge
    /usr/lib/ure/bin/regmerge XMyComp.rdb /UCR XMyComp.urd
  3. Implementación del servicio/interfaz en python
  4. Crear description.xml
  5. crear .xcu
  6. crear META-INF/MANIFEST.MF
  7. Empaquetar todo en zip, nombrandolo como myextension.oxt
  8. Instalar con unopkg
    /usr/lib/libreoffice/program/unopkg add myextension.oxt
    , ó abriendo la extensión con libreoffice.



Icon casestudy.gif

Añadiendo fórmulas a Calc

Empaquetar la extensión del ejemplo 6_calc_formulas_addin




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



Icon casestudy.gif

Ejecutando un job

Empaquetar y ejecutar el componente del ejemplo 8_writer_menutoolbar_job




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()



Icon casestudy.gif

Un diálogo para insertar texto

Instalar y ejecutar como macro el ejemplo 11_using_basicdialog




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