Trabajando con Apache OpenOffice

From Apache OpenOffice Wiki
< ES‎ | Manuales‎ | GuiaAOO‎ | TemasAvanzados‎ | Macros‎ | Python
Revision as of 17:57, 28 April 2013 by Mauriciobaeza (Talk | contribs)

Jump to: navigation, search


Conceptos iniciales

Este es el tema más importante para crear macros, de forma sencilla, pero se ven las bases para trabajar con todo AOO, por eso es importante no avanzar en estas notas, sin antes haber comprendido a cabalidad los temas presentes.

UNO (Universal Network Objects), es la especificación que da acceso a todos los objetos de Apache OpenOffice. Todo lo que quieras saber de esta especificación, lo puedes encontrar en la [Guía del desarrollador] (en ingles).

El API (Application Programer Interface) de Apache OpenOffice proporciona el acceso a toda la funcionalidad de AOO, independientemente del lenguaje. Todo el API de AOO esta en com.sun.star.module-ix (solo ingles).

La comunicación entre objetos UNO se basa en interfaces de objeto. Las interfaces pueden ser vistos desde el exterior o el interior de un objeto y proporcionan formas de ver o manipular el objeto.

Los servicios son agrupaciones lógicas de interfaces, son la forma en que un mismo objeto pueda ofrecer la funcionalidad de varias interfaces.

Los servicios se pueden implementar en librerías compartidas llamadas componentes UNO, con Python podemos crear componentes nuevos y generalmente el usuario final las recibe como una extensión OXT.

Los modulos pueden agrupar submodulos, servicios, excepciones, constantes, enumeraciones, etc, se utilizan para especificar bloques coherentes en la API, esto permite tener un API bien estructurada.

Creando servicios

Para crear un servicio, usamos la fabrica de servicios (com.sun.star.lang.XMultiComponentFactory), una interfaz especial para crear nuevos servicios, pero para llegar a ella, tenemos que hacerlo a traves de la interfaz ComponentContext (com.sun.star.uno.XComponentContext), como puedes verlo en la siguiente imagen.

ComponentContext y ServiceManager


En pyUNO tenemos dos formas de acceder a esta interfaz, las dos son equivalentes por lo que puedes usar la que prefieras. En la introducción a Python se mencionó que la mayoría de sus herramientas están disponibles en librerias, estás librerias tienen que importarse antes de poder usarse, en nuestro caso importaremos la librería uno que nos da acceso (entre otras cosas) al contexto de ejecución de AOO.

import uno
 
def pruebas():
    ctx1 = XSCRIPTCONTEXT.getComponentContext()
    ctx2 = uno.getComponentContext()
    return

Template:Documentation/Note


Ahora si, podemos acceder a la fábrica de servicios con el método getServiceManager().

import uno
 
def pruebas():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    return


Pero... ¿como sabemos que este objeto (ComponentContext) tiene este método getServiceManager()? porque lo estamos demostrando, si, también por eso, pero también porque puedes ver su documentación (com.sun.star.uno.XComponentContext) en el API de AOO, pero más importante aún, porque podemos hacer que el mismo objeto te diga todo lo que sabe hacer. Para ello haremos uso de una magnifica extensión que te tiene que acompañar el resto de tus días como programador de macros Python, nos referimos a MRI. Instala (tal vez tengas que reiniciar AOO para poder usarla) y la probamos de la siguiente manera.

import uno
 
def pruebas():
    ctx = uno.getComponentContext()
    # llamamos a la extensión MRI y le pasamos el objeto a inspeccionar
    mri(ctx)
    sm = ctx.getServiceManager()
    return 
 
def mri(target):
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()    
    mri = sm.createInstanceWithContext('mytools.Mri', ctx)
    mri.inspect(target)
    return


Observa en la imágen siguiente, como hemos seleccionado los métodos del objeto y nos lista todos los que tenga, hemos remarcado el que por ahora nos interesa getServiceManager(), observa al final de la línea que efectivamente este método nos devuelve una instancia de la interfaz com.sun.star.lang.XMultiComponentFactory.

Usando la extensión MRI


La gran ventaja de usar esta extensión (que por cierto esta hecha en Python), es que siempre te devolverá la información directamente del objeto, a esto se le llama instrospección. Acostumbrate a usarla.

Ahora si, ya podemos crear una instancia de cualquier servicio o interfaz disponible en AOO, por ejemplo, el servicio com.sun.star.frame.Desktop.

import uno
 
def pruebas():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
    return


Existe una forma alternativa de crear este servicio, usando XSCRIPTCONTEXT de la siguiente manera.

    XSCRIPTCONTEXT.getDesktop()

Template:Documentation/Note


¿Y para que sirve este servicio? Entre otras cosas para crear nuevos documentos.

nuevo = desktop.loadComponentFromURL('private:factory/scalc', '_default', 0, ())

Más adelante veremos a detalle este método y otros relacionados con los documentos de AOO.


El servicio com.sun.star.frame.Desktop es la instancia de la administración central para todas las aplicaciones AOO. Todas las ventanas de la aplicación AOO se organizan en una jerarquía de marcos que contienen componentes visibles.

Desktop administra los componentes y marcos.

Para los fines de crear macros es suficiente que recuerdes que, Desktop es el marco raíz de toda la jerarquía de documentos.


Para crear la instancia del servicio com.sun.star.frame.Desktop usamos un contexto y usamos el método createInstanceWithContext. Otros servicios no requieren de contexto para ser instanciados y usados, por ejemplo, el servicio com.sun.star.util.PathSettings que nos sirve para obtener las rutas de trabajo actuales en AOO, para estos casos usamos el método createInstance.

    rutas = sm.createInstance('com.sun.star.util.PathSettings')
    # mostramos la ruta de trabajo del usuario
    print (rutas.Work)


Ahora que ya sabemos instanciar servicios, veamos de nuevo nuestra macro para mostrar un mensaje en pantalla y analicemosla línea a línea para que veas que sencillo es, además, nos servirá para ver otros detalles al programar con Python en AOO. La macro tiene ahora unos ligeros cambios para que trabaje mejor y para explicar algunas cosas, veamos cuales.

def msgbox(message):
    from com.sun.star.awt import Rectangle
    from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK
 
    if not isinstance(message, (unicode, str)):
        message = str(message)
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    toolkit = sm.createInstanceWithContext('com.sun.star.awt.Toolkit', ctx)
    MsgBox = toolkit.createMessageBox(
                                    toolkit.getDesktopWindow(),
                                    Rectangle(),
                                    'infobox',
                                    BUTTONS_OK,
                                    'pyUNO',
                                    message)
    return MsgBox.execute()


En la primer línea declaramos nuestra macro, su nombre y los argumentos que necesita, en este caso solo uno message que será el mensaje a mostrar.

def msgbox(message):


En la segunda línea, estamos importando (import) la estructura Rectangle desde (from) el modulo com.sun.star.awt.module-ix.

    from com.sun.star.awt import Rectangle

Las estructuras son un conjunto de valores relacionados, valores que pueden ser de tipos diferentes, pero que son mostrados como una unidad lógica, por ejemplo, la estructura com.sun.star.awt.Rectangle hace referencia a un área rectangular estableciendo su posición y tamaño, en este caso, solo sirve para cumplir los argumentos del método para mostrar nuestro mensaje.


En la tercer línea, hacemos otra importación, pero esta vez la hacemos de una constante, específicamente la constante para mostrar el botón Aceptar (OK), estas constantes las puedes ver aquí: com.sun.star.awt.MessageBoxButtons

    from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK


La cuarta línea esta vacía solo para separar la parte de importacion con el resto del código. En la siguiente línea hacemos una validación del argumento pasado a nuestra macro, si el argumento no es una instancia de la clase unicode o de la clase str, entonces la convertimos a str, esto nos permite pasar a nuestra macro, listas, tuplas, diccionarios y practicamente cualquier objeto, del cual, podremos ver su representación en texto, muchas veces, con información bastante útil.

    if not isinstance(message, (unicode, str)):
        message = str(message)


La siguiente línea ya la hemos estudiado, creamos una instancia del contexto donde se ejecuta AOO.

    ctx = uno.getComponentContext()


La siguiente línea también ya la hemos estudiado, creamos una instancia de la fabrica de servicios, una de las instancias más importantes y que usaras constantemente.

    sm = ctx.getServiceManager()


En la siguiente línea creamos una instancia con contexto de un nuevo servicio com.sun.star.awt.Toolkit, este servicio sirve, entre otras muchas cosas para crear ventanas y mensaje de texto, como el de nuestra macro.

    toolkit = sm.createInstanceWithContext('com.sun.star.awt.Toolkit', ctx)


En la penultima línea, usamos el método createMessageBox del servicio com.sun.star.awt.Toolkit para crear nuestro cuadro de mensaje, todos los argumentos de este método puedes verlo aquí: com.sun.star.awt.XMessageBoxFactory, pero aquí los explicamos uno a uno.

    MsgBox = toolkit.createMessageBox(
                                    toolkit.getDesktopWindow(),
                                    Rectangle(),
                                    'infobox',
                                    BUTTONS_OK,
                                    'pyUNO',
                                    message)

Aunque veas muchas líneas, el interpete la considera una sola, esto se logra al estar todos los argumentos entre paréntesis.

  • El primer argumento es la ventana padre (parent) donde será mostrado el cuadro de mensaje, observa como usamos de nuevo el servicio toolkit para recuperar (getDesktopWindow) la ventana padre de AOO.
  • El segundo argumento es el área rectangular del cuadro de mensaje, observa los dos paréntesis al final del nombre de la estructura, son necesarios para inicializarla.
  • El tercer argumento es el tipo de mensaje, dependiendo de este argumento se muestra el icono correspondiente, los valores permitidos son: infobox, warningbox, errorbox, querybox y messbox, si usas infobox, el siguiente argumento es ignorado.
  • El cuarto argumento sirve para mostrar botones de comando diferentes, como: Si, No, Cancelar, etc, que están establecidos en las constantes vistas más arriba.
  • El quinto argumento es el título del cuadro de mensaje.
  • Y por ultimo, el mensaje a mostrar, el cual ya validamos.


En la ultima línea al mismo tiempo que mostramos (ejecute) estamos retornando el cuadro de mensaje, esto nos da la posibilidad de capturar el botón al que le haya dado click el usuario, lo cual, veremos más adelante. Todas las macros retornan un valor, aun y cuando este sea un valor vacío (None).

    return MsgBox.execute()


Resumiendo, los servicio los instanciamos y las estructuras y constantes las importamos, y el punto más importante; acostúmbrate a usar, manejar y dominar, la extensión MRI, ella te dice todo lo necesario para trabajar con los objetos UNO de AOO, los demás, es sencillo.


El documento activo

Tenemos dos formas de acceder al documento activo.

    doc1 = XSCRIPTCONTEXT.getDocument()
    doc2 = desktop.getCurrentComponent()
    print (doc1.getIdentifier())
    print (doc2.getIdentifier())

Template:Documentation/Note


Si seguiste nuestra recomendación de asignar la macro de pruebas a una combinación de teclas disponible en todo AOO, prueba a abrir cada uno de los tipos de documentos que puedes ver en el menú Archivo->Nuevo->.

Tipos de documentos en AOO


Y a ejecutar la siguiente macro, teniendo como documento activo cada uno de ellos, es decir, activas uno y ejecutas la macro, activas otro y vuelves a llamar a la macro y así sucesivamente con todos.

def pruebas():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
    doc = desktop.getCurrentComponent()
    print (doc.getIdentifier())
    return


Los mensajes devueltos en cada documento, son bastante ilustrativos de que tipo de documento se trata. Pero si probaste con todos, habrás notado que con la base de datos nos devuelve un error, si no lo has probado hazlo ahora. Para resolver este caso tenemos varias posibilidades, la primera es controlar la excepción como ya aprendimos en la introducción a Python.

    doc = desktop.getCurrentComponent()
    try:
        print (doc.getIdentifier())
    except:
        print ('Error al obtener el tipo de documento')


Pero... ¿estamos realmente seguros que ese es el error?, es mejor especificarlo y asegurarnos de que este es el error.

    doc = desktop.getCurrentComponent()
    try:
        print (doc.getIdentifier())
    except AttributeError:
        print ('Error al obtener el tipo de documento')


Tenemos otra alternativa donde no usamos controlador de excepciónes, si no una función que nos dice si un cierto atributo lo posee o no un objeto.

    doc = desktop.getCurrentComponent()
    if hasattr(doc, 'getIdentifier'):
        print (doc.getIdentifier())
    else:
        print ('Sin atributo getIdentifier')


Es mucho mejor esta segunda opción. Aun así, no podemos estar 100% seguros de que cuando no la tenga, el documento sea una base de datos, simplemente estamos excluyendo la posibilidad pero no lo estamos asegurando, probemos con otra propiedad.

    doc = desktop.getCurrentComponent()
    print (doc.getImplementationName())


Mucho mejor, ahora ya no tenemos errores, pero si probaste de nuevo con todos los tipos de documentos, habrás notado que con Impres y con Draw obtenemos el mismo resultado, por lo que no tenemos la certeza de que tipo de documento es, por lo que descartamos esta propiedad para nuestra finalidad que es identificar fehacientemente cada tipo de documento.

Ya vimos que cada objeto UNO soporta diferentes servicios e interfaces, asì que cada documento de AOO (que son objetos) soportan diferentes servicios, algunos son iguales, es decir, comúnes a todos, pero algunos pueden ser únicos para cada documento. Ya vimos como saber si un documento soporta una propiedad, pero para saber si soporta un servicio completo, cambiamos de método, ahora usamos supportsService que nos devuelve falso o verdadero según soporte o no el nombre del servicio pasado como argumento.

    res = doc.supportsService('com.sun.star.sheet.SpreadsheetDocument')
    mensaje = u'No soy una hoja de cálculo'
    if res:
        mensaje = u'Soy una hoja de cálculo'
    print(mensaje)


Y mejor aun, podemos saber todos los servicios que soporta un objeto con el método getSupportedServiceNames que nos devuelve una tupla con todos sus nombres.

    doc = desktop.getCurrentComponent()
    servicios = doc.getSupportedServiceNames()
 
    print(servicios)


Te queda de tarea probar esta macro en cada uno de los documentos abiertos, incluso puedes probar también el IDE para AOOBasic que incorpora AOO y determinar que servicio usar para diferenciar sin duda cada tipo de documento.

Los documentos abiertos

Para acceder a todos los documentos abiertos en la sesión de AOO, usamos el método getComponents del objeto Desktop, si usas MRI, notarás que este método nos devuelve una instancia de la interfaz com.sun.star.container.XEnumerationAccess

    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
    documentos = desktop.getComponents()


Esta interfaz (com.sun.star.container.XEnumerationAccess) tiene un solo método llamado createEnumeration que nos duelve una instancia de la interfaz com.sun.star.container.XEnumeration la cual a su vez tiene solo dos métodos, el primero para saber si tenemos más elementos en la enumeración: hasMoreElements y el segundo para acceder al siguiente elemento nextElement, veamoslo con un ejemplo.

def pruebas():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
 
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
    documentos = desktop.getComponents()
    enum = documentos.createEnumeration()
    # mientras tengamos elementos
    while enum.hasMoreElements():
        # obtenemos el siguiente elemento
        doc = enum.nextElement()
        # mostrámos el tipo de documento
        mensaje = obtener_tipo(doc)
        print(mensaje)
    return 
 
def obtener_tipo(doc):
    tipo = {'com.sun.star.sheet.SpreadsheetDocument': 'Calc',
            'com.sun.star.text.TextDocument': 'Writer',
            'com.sun.star.presentation.PresentationDocument': 'Impress',
            'com.sun.star.drawing.DrawingDocument': 'Draw',
            'com.sun.star.sdb.OfficeDatabaseDocument': 'Base',
            'com.sun.star.formula.FormulaProperties': 'Math',
            'com.sun.star.script.BasicIDE': 'Basic'}
    # iteramos entre los tipos de documentos
    for t in tipo:
        # validamos si soporta el servicio
        if doc.supportsService(t):
            # devolvemos el tipo de documento
            return 'Soy %s' % tipo[t]
    # si termina sin encontrar un tipo
    return 'No se que tipo soy'


Observa como usamos una macro de apoyo para saber que tipo de documento es. Una enumeración no es más que una lista de objetos y es una forma muy común y a veces la única de acceder a ciertos objetos en AOO, es importante que la recuerdes.

Rutas de archivos y directorios

Apache OpenOffice, al ser multiplataforma, hace uso de las rutas de archivos y directorios, en el formato URL, por ejemplo.

file:///home/usuario/datos/miarchivo.ods

file:///d:/datos/miarchivo.ods


Toma en cuenta que este formato hace uso de los caracteres 0-9, a-z y A-Z, y cualquier otro carácter será convertido y mostrado con su respectivo código, por ejemplo, los espacios los reemplazara con %20. Siempre que le pases una ruta de archivo a cualquier servicio de AOO, hazlo, casi como una regla, pasarle la ruta en formato Url, para ello, la libreria uno cuenta con dos funciones muy útiles que hacen el trabajo systemPathToFileUrl y fileUrlToSystemPath, veamos como se usan.

import uno
 
def rutas():
 
    ruta = "/home/mau/Mi archivo de Calc.ods"
    print (ruta)
 
    # La convertimos al formato URL
    ruta = uno.systemPathToFileUrl(ruta)
    print (ruta)
 
    # Regresamos al formato local
    ruta = uno.fileUrlToSystemPath(ruta)
    print (ruta)
 
    return


Abre varios documentos existentes y ejecuta la siguiente macro desde cada uno:

import uno
import os
 
def info_ruta():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)    
 
    doc = desktop.getCurrentComponent()
    # obtenemos la ruta en disco
    ruta =  doc.getURL()
    # obtenemos las diferentes partes de la ruta
    ruta, archivo = os.path.split(ruta)
    nombre, extension = os.path.splitext(archivo)
 
    print (ruta)
    print (uno.fileUrlToSystemPath(ruta))
    print (archivo)
    print (nombre)
    print (extension)
 
    return


Hemos importado una nueva librería de Python, os que entre otras muchas cosas, nos permite obtener información de las rutas de archivos como en la macro anterior.

En nuestros siguientes temas, veremos la importancia de usar las rutas de archivos y directorios en formato Url y recuerda que en S.O. Linux, se tiene en cuenta la diferencia entre mayúsculas y minúsculas.

Creando nuevos documentos

El método que nos permite crear nuevos documentos se llama loadComponentFromURL y es un método del servicio com.sun.star.frame.XComponentLoader, por ejemplo, para crear una nueva hoja de calculo.

    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)    
 
    ruta = 'private:factory/scalc'
    nuevo_doc = desktop.loadComponentFromURL(ruta, '_default', 0, ())


Solo hay que hacer un pequeño cambio para crear un nuevo documento de texto.

    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)    
 
    ruta = 'private:factory/swriter'
    nuevo_doc = desktop.loadComponentFromURL(ruta, '_default', 0, ())


Y los demás tipos de documentos.

    ruta = 'private:factory/simpress'
    nuevo_doc = desktop.loadComponentFromURL(ruta, '_default', 0, ())
 
    ruta = 'private:factory/sdraw'
    nuevo_doc = desktop.loadComponentFromURL(ruta, '_default', 0, ())
 
    ruta = 'private:factory/smath'
    nuevo_doc = desktop.loadComponentFromURL(ruta, '_default', 0, ())


¿Y una base de datos?, probemos.

    ruta = 'private:factory/sbase'
    nuevo_doc = desktop.loadComponentFromURL(ruta, '_default', 0, ())


Nos da un error ¿verdad?

Error al crear base de datos


No esta soportado. Para saber por que nos da un error, intenta abrir una base de datos nueva, observa como primero, si queremos trabajar con ella, el asistente nos “obliga” primero a guardarla con un nombre y después ya podemos manipularla, esto, solo pasa con las bases de datos, de los demás documentos podemos crear tantos nuevos documentos como queramos sin tener que guardar nada, entonces ¿como creamos una nueva base de datos?, la respuesta es sencilla, del mismo modo que lo hacemos desde la interfaz de usuario, es decir, tenemos que guardar primero la base de datos.

    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)    
 
    ruta = "/home/mau/Mi_Base_Datos.odb"
    # Creamos el servicio que nos permite manipular bases de datos
    dbcon = sm.createInstance('com.sun.star.sdb.DatabaseContext')
    # creamos una instancia de una nueva base de datos
    base = dbcon.createInstance()
    # establecemos el tipo de base
    base.URL = "sdbc:embedded:hsqldb"
    # y la guardamos
    base.DatabaseDocument.storeAsURL(ruta, ())


Toma en cuenta dos cosas muy importantes en la macro anterior, el nombre que uses, si ya existe, simplemente lo reemplazara, no te preguntara nada, así que ten cuidado de no usar una ruta o nombre de una base importante que tengas y segundo, la base de datos así creada, no quedara “registrada” en AOO, con lo que no tendrás acceso desde otros programas de la aplicación, más adelante veremos como registrar una base de datos por código.

Abriendo, guardando y cerrando documentos

Abrir archivos existentes es sumamente sencillo, usamos el mismo método usado para crear nuevos documentos com.sun.star.frame.XComponentLoader, pero en el parámetro ruta, le especificamos la ubicación del archivo a abrir de la siguiente manera.

def documento1():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Calc.ods')
    doc_c = desktop.loadComponentFromURL(ruta, '_default', 0, ())
 
    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Writer.odt')
    doc_w = desktop.loadComponentFromURL(ruta, '_default', 0, ())
 
    return


Si tus archivos existen (esto se puede validar), el código anterior los abrirá, siempre utiliza uno.systemPathToFileUrl cuando uses rutas de archivos o directorios en funciones de AOO que las requieran como argumentos. Ya vimos que el primer agumento del método loadComponentFromURL, es la ruta del archivo a abrir, el segundo argumento es una cadena con el nombre del macro (frame) donde será mostrado el documento abierto, generalmente si existe este frame se usa si no se crea uno nuevo.

    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Calc.ods')
    doc_c = desktop.loadComponentFromURL(ruta, 'nuevo_cal', 0, ())
 
    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Writer.odt')
    doc_w = desktop.loadComponentFromURL(ruta, 'nuevo_writer', 0, ())


Existen nombre especiales para este agumento, los más usuales son dos, primero que ya hemos estado usando _default que usa el frame actual y _blank que siempre crea un nuevo frame.

    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Calc.ods')
    doc_c = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Writer.odt')
    doc_w = desktop.loadComponentFromURL(ruta, '_blank', 0, ())


La recomendación es que uses siempre _default o _blank, un escenario puede ser que habrás con _default una instancia, y después de forma oculta uno nuevo con _blank, más abajo vemos como abrir un documento de forma oculta.


Si abres una plantilla, esta se comportará de forma normal, es decir, te abrirá una copia de dicha plantilla.

    ruta = uno.systemPathToFileUrl('/home/mau/Mi_plantilla.ots')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())


Pero también podrías querer abrir la plantilla para editarla, en este caso, le indicamos en las opciones de apertura esta opción, y aquí vemos como usar el cuarto argumento del método loadComponentFromURL.

    from com.sun.star.beans import PropertyValue
 
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    opciones = PropertyValue()
    opciones.Name = 'AsTemplate'
    opciones.Value = False
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo.ots')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Observa como hemos importado una nueva estructura com.sun.star.beans.PropertyValue.

    from com.sun.star.beans import PropertyValue


Observa como la instanciamos y asignamos sus valores.

    opciones = PropertyValue()
    opciones.Name = 'AsTemplate'
    opciones.Value = False


Muy importante, el cuarto argumento del método loadComponentFromURL espera una tupla, por eso es que usamos la coma al final del nombre del argumento (opciones,)

    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


La estructura com.sun.star.beans.PropertyValue esta conformada por un par de valores, un nombre y un valor y es muy frecuentemente usada cuando se programa en AOO, para este ejemplo solo hemos usado un par de agumentos, siempre van en pares, pero muy bien puede tener más opciones como veremos más adelante. En la macro anterior le estamos indicando la propiedad AsTemplate (que seria algo así como Es Plantilla), si el valor es falso (False), le estamos indicando que queremos abrir la plantilla para editarla, en caso contrario (True) abrirá una copia de la plantilla. Lo interesante de esta propiedad es que perfectamente la puedes aplicar a archivos existentes, es decir, archivos que no necesariamente sean plantillas, con esta propiedad podemos forzarlos a comportarse como plantillas, por ejemplo, prueba el siguiente código con algún archivo existente que no sea plantilla.

    opciones = PropertyValue()
    opciones.Name = 'AsTemplate'
    opciones.Value = True
 
    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Writer.odt')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Observa como el archivo es un documento de texto normal (ODT), pero con la propiedad AsTemplate, forzamos su apertura como una plantilla.

Otras propiedades interesantes son ReadOnly (Solo Lectura) para abrir un archivo como solo lectura.

    opciones = PropertyValue()
    opciones.Name = 'ReadOnly'
    opciones.Value = True
 
    ruta = uno.systemPathToFileUrl('/home/mau/Mi_archivo_de_Writer.odt')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Y Password (Contraseña) para abrir archivos con contraseña.

    opciones = PropertyValue()
    opciones.Name = 'Password'
    opciones.Value = 'letmein'
 
    ruta = uno.systemPathToFileUrl('/home/mau/Archivo_con_contrasena.odt')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Por supuesto puedes combinar cualquier cantidad de opciones siempre y cuando no entren en conflicto, es importante que al final, sea una tupla lo que le pasas al método, si no, te dará un error.

    # creamos una lista vacía
    opciones = []
    # creamos la primer propiedad
    opcion = PropertyValue()
    opcion.Name = 'ReadOnly'
    opcion.Value = True
    # y la agregamos a la lista
    opciones.append(opcion)
    # creamos la segunda propiedad
    opcion = PropertyValue()
    opcion.Name = 'Password'
    opcion.Value = 'letmein'
    # y la agregamos a la lista
    opciones.append(opcion)
 
    ruta = uno.systemPathToFileUrl('/home/mau/Archivo_con_contrasena.odt')
    # nota que la lista la convertimos en tupla
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, tuple(opciones))


Si la contraseña es incorrecta, no obtendrás ningún mensaje, simplemente no lo abrira y la variable doc será vacía, puedes validar esto:

    if doc is None:
        print ('No se abrio el documento')
    return


Más adelante veremos otras formas de validar y depurar nuestras macros para los dificiles casos en donde no obtengamos ningún mensaje de error, pero simplemente nuestra macro se niega a hacer su trabajo.


Mientras un formato de archivo sea soportado por AOO, lo podrás abrir con el método loadComponentFromURL, en el siguiente ejemplo, abrimos un archivo tipo Doc, un Html y un Xls.

    ruta = uno.systemPathToFileUrl('/home/mau/archivo_word.doc')
    doc_w = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_web.html')
    doc_h = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_excel.xls')
    doc_x = desktop.loadComponentFromURL(ruta, '_blank', 0, ())


¿Qué otras opciones tengo para abrir archivos?. La mayoría están descritas en el servicio com.sun.star.document.MediaDescriptor, aquí veremos las más usadas.


Cuando abres un archivo por código y este contiene macros, tu puedes establecer si se abre con las macros activadas, de forma predeterminada lo abre con las macros desactivadas, así que solo nos resta saber como abrirlo con las macros activadas, las constanes que puedes usar puedes verlas aquí com.sun.star.document.MacroExecMode

    from com.sun.star.document.MacroExecMode import ALWAYS_EXECUTE_NO_WARN
 
    opciones = PropertyValue()
    opciones.Name = 'MacroExecutionMode'
    opciones.Value = ALWAYS_EXECUTE_NO_WARN
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc.ods')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Otra opción interesante, es la que nos permite abrir una presentación e iniciarla inmediatamente.

    opciones = PropertyValue()
    opciones.Name = 'StartPresentation'
    opciones.Value = True
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_impress.odp')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Puedes abrir un archivo como vista previa, útil para evitar que el usuario haga cambios en el, en este estado, los cambios que intente hacer el usuario no se guardaran.

    opciones = PropertyValue()
    opciones.Name = 'Preview'
    opciones.Value = True
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_writer.odt')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))


Por supuesto, la idea general de abrir un archivo es para obtener datos de el o para manipularlo, ya sea un documento nuevo o uno existente, veamos como guardar los cambios realizados y cerrar el archivo.

    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc.ods')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    # aquí va todo el código que quieras para manipular el archivo
    msgbox('Archivo abierto correctamente, presiona Aceptar para gaurdarlo y cerrarlo')
 
    # guardamos los cambios
    doc.store()
    # cerramos el archivo
    doc.close(True)


El código anterior funcionara con un archivo existente, pero con uno nuevo no, te dará un error, por supuesto no me creas, compruébalo. Los documentos tienen un método (hasLocation) que nos informa si un documento ya esta guardado o no.

    ruta = 'private:factory/scalc'
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
    msgbox(doc.hasLocation())
    doc.close(True)
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc.ods')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
    msgbox(doc.hasLocation())
    doc.close(True)


El primer mensaje debe mostrarte falso (False) porque es un documento nuevo y el segundo verdadero (True) por ser un archivo abierto del disco.


También tenemos una propiedad que nos ayuda a saber si un documento ha sido modificado o no, ejecuta la siguiente macro, llamándola desde diferentes documentos, unos ábrelos y modifícalos antes de ejecutarla, otros, solo ábrelos y ejecútala para que notes la diferencia.

def esta_modificado():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
    doc = desktop.getCurrentComponent()
 
    msgbox(doc.isModified())
    return


Y otra más que nos informa si el documento es de solo lectura (isReadonly).

    ruta = 'private:factory/scalc'
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
    msgbox(doc.isReadonly())
 
    opciones = PropertyValue()
    opciones.Name = 'ReadOnly'
    opciones.Value = True
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc.ods')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))
    msgbox(doc.isReadonly())


Es importante que valides antes de intentar guardar un archivo, si este ha sido modificado o es de solo lectura. Ahora veamos como guardar un archivo nuevo con el método storeAsUrl.

    ruta = 'private:factory/scalc'
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    msgbox('Ahora guardaremos el archivo')
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_nuevo_calc.ods')
    doc.storeAsURL(ruta, ())


Observa como requiere dos parámetros, la ruta del archivo y una matriz de propiedades, algunas de las cuales veremos a continuación. Toma nota de que si el archivo ya existe lo reemplazara sin avisarte por lo que es buena practica de programación validar esto, observa como volvemos a usar la librería os para saber si un archivo existe ya.

import uno
import os
 
def pruebas():
    documentos()
    return
 
def documentos():
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    ruta = 'private:factory/scalc'
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    msgbox('Ahora guardaremos el archivo')
 
    ruta = '/home/mau/archivo_nuevo_calc.ods'
    if os.path.exists(ruta):
        msgbox(u'El archivo existe, no se guardará el nuevo')
        return
 
    ruta = uno.systemPathToFileUrl(ruta)
    doc.storeAsURL(ruta, ())
    return


De la macro anterior, es muy importante que tomes nota que para todos los métodos de Python que usen rutas de archivos o directorios, se les tienen que pasar en las rutas nativas del sistema operativo donde estes trabajando, solo a los métodos de AOO, es a los que se les pasa en notación URL.


Veamos algunas propiedades interesantes para guardar archivos, por ejemplo, guardar con contraseña.

    from com.sun.star.beans import PropertyValue
 
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    ruta = 'private:factory/scalc'
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    msgbox(u'Ahora guardaremos el archivo pero con contraseña')
 
    opciones = PropertyValue()
    opciones.Name = 'Password'
    opciones.Value = 'letmein'
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_protegido.ods')
    doc.storeAsURL(ruta, (opciones,))


Como sabes, AOO, soporta la importación/exportación de múltiples formatos de archivos, veamos como guardar, por ejemplo, en formato DOC.

    from com.sun.star.beans import PropertyValue
 
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    ruta = 'private:factory/swriter'
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    msgbox(u'Ahora guardaremos el archivo en formato DOC')
 
    opciones = PropertyValue()
    opciones.Name = 'FilterName'
    opciones.Value = 'MS Word 97'
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_word.doc')
    doc.storeAsURL(ruta, (opciones,))


Es importante que cuando exportes, uses un formato compatible, es decir, no puedes guardar una hoja de calculo como documento de texto. En el Apéndice mostramos una macro para obtener todos los filtros que soporta AOO.


El método storeAsURL también lo puedes usar para abrir un archivo existente y guardarlo con otro nombre.

    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc.ods')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
 
    msgbox(u'Ahora guardaremos con otro nombre')
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc_nuevo.ods')
    doc.storeAsURL(ruta, ())


Para terminar esta sección, vamos a hacer un poco más interactiva la apertura y guardado de archivos, permitamos que el usuario escoja la ruta y el archivo desde un cuadro de dialogo, para ello, usaremos el servicio com.sun.star.ui.dialogs.FilePicker, que nos permite hacer esto.

    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    dlg = sm.createInstance('com.sun.star.ui.dialogs.FilePicker')
    # podemos establecer el título
    dlg.setTitle('Selecciona un archivo')
    if dlg.execute():
        # el primer elemento es la ruta seleccionada
        ruta = dlg.getFiles()[0]
        doc = desktop.loadComponentFromURL(ruta, '_blank', 0, ())
    else:
        msgbox('Proceso cancelado')


Si estableces la propiedad MultiSelectionMode en verdadero, le permitiras al usuario seleccionar más de un archivo.

    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    dlg = sm.createInstance('com.sun.star.ui.dialogs.FilePicker')
    dlg.setTitle('Selecciona un archivo')
    dlg.setMultiSelectionMode(True)
    if dlg.execute():
        archivos = dlg.getFiles()
        if len(archivos) == 1:
            # si selecciona uno, devuelve la ruta completa
            desktop.loadComponentFromURL(archivos[0], '_blank', 0, ())
        else:
            # si selecciona varios, el primer elemento es la carpeta
            folder = archivos[0]
            # los demás elementos son los archivos seleccionados
            for f in range(1, len(archivos)):
                desktop.loadComponentFromURL(folder + archivos[f], '_blank', 0, ())
    else:
        msgbox('Proceso cancelado')


Si lo notaste, con la macro anterior el usuario puede seleccionar cualquier archivo, para limitar las opciones, agrega un filtro con el método appendFilter con solo los tipos de archivos que necesites, usa una línea por cada filtro, este método requiere dos argumentos, el primero es el título, lo que verá el usuario y el segundo es el filtro en si.

    dlg.appendFilter('Hoja de calculo ODF (.ods)', '*.ods')


Ahora, mostramos el cuadro de dialogo Guardar Como, para permitir al usuario seleccionar carpeta y nombre de archivo donde guardar su archivo, se usa el mismo servicio com.sun.star.ui.dialogs.FilePicker solo cambiando algunas de sus propiedades.

    from com.sun.star.ui.dialogs.TemplateDescription import FILESAVE_AUTOEXTENSION_PASSWORD
 
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    dlg = sm.createInstance('com.sun.star.ui.dialogs.FilePicker')
    dlg.setTitle('Guardar archivo')
    dlg.appendFilter('Hoja de calculo ODF (.ods)', '*.ods')
    # con esta opción le indicamos que use guardar como con otras opciones
    dlg.initialize((FILESAVE_AUTOEXTENSION_PASSWORD,))
    if dlg.execute():
        ruta = dlg.getFiles()[0]
        msgbox(ruta)


Nota que el argumento del método initialize es una tupla.


Ahora si, para terminar, te mostramos como abrir un archivo de forma oculta.

    from com.sun.star.beans import PropertyValue
 
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    opciones = PropertyValue()
    opciones.Name = 'Hidden'
    opciones.Value = True
 
    ruta = uno.systemPathToFileUrl('/home/mau/archivo_calc.ods')
    doc = desktop.loadComponentFromURL(ruta, '_blank', 0, (opciones,))
 
    msgbox('Archivo abierto de forma oculta, ahora lo cerraremos')
 
    doc.store()
    doc.close(True)


Es muy importante, que cuando abras un archivo oculto siempre uses el argumento _blank, a menos claro esta, que sepas lo que haces, y que, mantengas una referencia a el, para poder cerrarlo en cuanto no lo necesites.

Algunas tareas comunes en documentos

Exportando a PDF

Para exportar a PDF solo hay que establecer el filtro correcto, nota como construimos el nombre del filtro de acuerdo al tipo de documento y como construimos la ruta de acuerdo a la ruta actual donde este guardado el documento solo reemplazando la extensión para que quede en PDF. Estamos haciendo uso de la macro obtener_tipo con unos ligeros cambios para este propósito.

import uno
import os
 
def pruebas():
    export_pdf()
    return
 
def export_pdf():
    from com.sun.star.beans import PropertyValue
 
    ctx = uno.getComponentContext()
    sm = ctx.getServiceManager()
    desktop = sm.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
 
    doc = desktop.getCurrentComponent()
    tipo = obtener_tipo(doc)
    soportados = 'calc,writer,draw,impress,math'
    if tipo in soportados:
        # establecemos el filtro como PDF
        opciones = PropertyValue()
        opciones.Name = 'FilterName'
        opciones.Value = '%s_pdf_Export' % tipo
        # obtenemos la ruta y reemplazados la extensión por PDF
        ruta, archivo = os.path.split(doc.getURL())
        nombre, _ = os.path.splitext(archivo)
        ruta = os.path.join(ruta, nombre + '.pdf')
        # guardamos
        doc.storeToURL(ruta, (opciones,))
        msgbox(u'Documento exportado correctamente a PDF')
    else:
        msgbox(u'Aplicación no soportada')
    return
 
def obtener_tipo(doc):
    tipo = {'com.sun.star.sheet.SpreadsheetDocument': 'calc',
            'com.sun.star.text.TextDocument': 'writer',
            'com.sun.star.presentation.PresentationDocument': 'impress',
            'com.sun.star.drawing.DrawingDocument': 'draw',
            'com.sun.star.sdb.OfficeDatabaseDocument': 'base',
            'com.sun.star.formula.FormulaProperties': 'math',
            'com.sun.star.script.BasicIDE': 'basic'}
    for t in tipo:
        if doc.supportsService(t):
            return tipo[t]
    return 'No se que tipo soy'

Ocultando y mostrando ventanas

Controlando la barra de estado

Personal tools