23.08.2010

Categoria(s)
Tutoriais

Tag(s)

Tutorial de macros em Python no OpenOffice.org


por lfagundes 5 comentários


De vez em quando alguém nos consulta para o desenvolvimento de um sistema de gestão para a sua empresa, com um argumento bastante comum: “Preciso de um sistema de gestão, pois hoje tenho tudo em planilhas”. Nós aqui também temos tudo em planilha e estamos bastante satisfeitos com a nossa organização, mas depois de um ano crescendo e estruturando nossa planilha começamos a utilizar macros para manter a sua consistência. Com base nessa experiência, resolvi escrever este tutorial de como integrar código Python ao OpenOffice.org.

O foco do tutorial é trabalhar com planilhas, mas ele se aplica a outros tipos de documento. Foi desenvolvido no Ubuntu 9.10.

Nota: se você está interessado neste tutorial, veja também esse post mais recente. Todo o conteúdo deste tutorial foi abstraído na biblioteca OOSheet, publicada depois.

Hello World do PyUNO

Primeiramente, instale o ipython e python-uno. No Ubuntu:

aptitude install ipython python-uno

Certifique-se de que o OpenOffice não esteja rodando e abra uma nova planilha com a seguinte linha de comando:

oocalc -accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"

Dessa forma o openoffice pode ser controlado remotamente por uma conexão na porta 2002. Usaremos esse modo apenas para testar e desenvolver o script antes que ele seja embutido em um documento.

Rode o comando “ipython“. Um shell de python será aberto. Digite as seguintes linhas:

1
2
3
4
5
6
7
import uno
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)
document = desktop.getCurrentComponent()

A partir desse objeto document, podemos manipular a planilha. Por exemplo, tente:

8
9
cell = document.Sheets.Planilha1.getCellByPosition(0,0)
cell.setString('Hello World!')

OBS: Se o seu office tiver em inglês, o nome padrão da primeira aba vai ser “Sheet1″ e não “Planilha1″, portanto você deve usar “document.Sheets.Sheet1″ para acessá-la.

Pronto, está feito o Hello World :-). Na próxima etapa, vamos fazer algumas manipulações de dados pelo ipython.

Manipulando dados

Um documento de planilha tem várias abas (Sheets) e cada aba tem várias células (Cells). Cada célula tem um valor, um string e uma fórmula, que podem ser todos iguais, mas também podem ser diferentes. Por exemplo, teste esta sequência:

10
11
12
13
cell.setFormula(10)
cell.getValue()
cell.getString()
cell.getFormula()

Repare que cell.getValue() retorna um float, enquanto os outros dois retornam um string, mas essencialmente as três funções retornam a mesma coisa.

Vejamos outro experimento:

14
15
16
17
18
cell2 = getCellByPosition(1, 0)
cell2.setFormula('=A1+5')
cell2.getValue()
cell2.getString()
cell2.getFormula()

Neste podemos ver que a fórmula é diferente do string e do valor.

Antes do próximo experimento, mude a formatação da célula B1 para uma data em qualquer formato (com o botão direito do mouse mesmo, direto na planilha). Execute as seguintes linhas no shell do python:

19
20
21
22
cell2.setValue(3700)
cell2.getValue()
cell2.getString()
cell2.getFormula()

Neste último teste vemos que o valor e string são diferentes. Pra terminar esta série, veja um exemplo em que fórmula, valor e string são diferentes:

23
24
25
26
cell2.setFormula('=A1+3700')
cell2.getValue()
cell2.getString()
cell2.getFormula()

Com essas rotinas de get e set para fórmula, valor e string pode-se fazer muita coisa. É interessante notar que as datas são armazenadas como números. Para manipulá-las em python é bom saber que a data representada pelo zero é 30/12/1899, com isso pode-se fazer conversões entre objetos datetime e datas da planilha:

27
28
29
30
from datetime import datetime, timedelta
basedate = datetime(1899, 12, 30)
delta = datetime.now() - basedate
cell2.setValue(delta.days)

Criando macros em python

Para ser utilizado como uma macro do OpenOffice, um script python deve estar em uma das seguintes localizações: no diretório do usuário do OpenOffice, no diretório compartilhado do OpenOffice ou dentro do documento. Os dois primeiros são mais fáceis mas tornam o compartilhamento da macro mais difícil, vamos tratar aqui do terceiro.

Primeiramente, salve aquela planilha em um arquivo .ods e crie um arquivo hello_world.py com o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python
#coding: utf-8
 
import uno
 
def hello():
document = XSCRIPTCONTEXT.getDocument()
cell = document.Sheets.Planilha1.getCellByPosition(0, 0)
cell.setString('Hello World')

Repare que na macro a rotina para obter o objeto document é bem diferente (e bem mais simples), mas o funcionamento é igual.

O OpenOffice não fornece interface para editar macros dentro de um documento. Os documentos do OpenOffice são arquivos zip contendo vários arquivos em formato XML, para criar uma macro é necessário descompactar o arquivo, modificá-lo e compactá-lo novamente.

Para descompactar o arquivo é necessário criar um diretório, pois o zip contém todos os arquivos na raiz:

mkdir minhaplanilha
cd minhaplanilha
unzip ../minhaplanilha.ods

Crie um diretorio para os scripts e coloque o seu script python lá dentro:

mkdir -p Scripts/python
cp /algumlugar/hello_world.py Scripts/python

Edite o arquivo META-INF/manifest.xml e adicione linhas contendo os diretórios criados e novos arquivos. Sem isso, quando o OpenOffice salvar o documento os scripts serão perdidos. Abaixo um modelo de como deve ficar o arquivo, foram adicionadas três linhas no final:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
 <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.spreadsheet" manifest:version="1.2" manifest:full-path="/"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/thumbnail.png"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/current.xml"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/progressbar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/floater/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/popupmenu/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/menubar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/toolbar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/Bitmaps/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/statusbar/"/>
 <manifest:file-entry manifest:media-type="application/vnd.sun.xml.ui.configuration" manifest:full-path="Configurations2/"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/python/hello_world.py"/>
 <manifest:file-entry manifest:media-type="application/binary" manifest:full-path="Scripts/python/"/>
 <manifest:file-entry manifest:media-type="application/binary" manifest:full-path="Scripts/"/>
</manifest:manifest>

Compacte novamente o arquivo:

zip -r ../minhaplanilha.ods *

Se você abrir o novo arquivo no OpenOffice agora, aparecerá um alerta sobre o perigo das macros. Certamente não é desejável executar um script python desconhecido ao abrir um documento qualquer recebido por e-mail, portanto é necessário acertar as configurações de segurança em todas as instalações de OpenOffice que forem utilizar as macros.

Escolha um diretório para guardar documentos confiáveis e coloque este documento naquele diretório. No OpenOffice, vá em Tools -> Options -> OpenOffice.org -> Security -> Macro Security (ou Ferramentas -> Opções -> BrOffice.org -> Segurança -> Segurança de Macros). Na aba “Trusted Sources” (Fontes Confiáveis) adicione o diretório escolhido. É necessário reiniciar o OpenOffice.

Abra a planilha, vá em Tools -> Macros -> Run Macro… (Ferramentas -> Macros -> Executar Macro…) e veja as macros do novo documento (há também as “Minhas Macros” e “OpenOffice.org Macros”). Selecione a macro chamada “hello” e clique em “Run” (Executar).

E pronto! O conteúdo da célula A1 da primeira aba deve ser “Hello World!”.

Rodando a macro

Já conseguimos criar uma macro e rodá-la, porém rodar do menu toda vez pode não ser muito conveniente. Sugiro duas outras maneiras que podem ser úteis.

Primeiro, pode-se associar uma macro a um evento. Para tal, vá em “Tools -> Customize” (ou “Ferramentas -> Personalizar”) na aba “Events” (ou “Eventos”), pode-se escolher um evento e associar à sua macro.

Segundo, pode-se criar um botão para rodar a macro:

  • Vá em “View -> Toolbars -> Form Controls”. Uma caixa de edição será aberta.
  • Clique no segundo ícone, de apertar um botão, isso ligará o modo de edição.
  • Clique e arraste o mouse sobre a planilha para desenhar um botão.
  • Para configurar o botão, clique nele com o botão direito e selecione a opção “Control”. A caixa de propriedades será aberta
    • Na aba “General”, pode-se mudar o texto que aparece no botão, além de diversas propriedades
    • Na aba “Events”, associe uma macro a um evento do botão
  • Para que o botão funcione, é necessário desligar o modo de edição dos botões. Clique no mesmo ícone de abertar o botão da primeira caixa de edição.

Feche a caixa de edição e clique no botão para testar sua macro.

Desenvolvendo macros continuamente

Uma vez que você tenha feito uma macro e inserido no arquivo, vai perceber que testar e debugar uma rotina em python dentro do documento pode ser uma tarefa árdua, pois para cada modificação é necessário fechar o OpenOffice, descompactar o arquivo ods, colocar modificar o python, zipar novamente, abrir o OpenOffice e rodar a macro.

Para agilizar o desenvolvimento, duas dicas. Primeiro, crie um shellscript que automatize o processo de inserir o script no documento. Algo como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
 
DOCUMENTO=mydocument.ods
SCRIPT=mymacro.py
 
mkdir document_temp
cd document_temp
unzip ../$DOCUMENTO
mkdir -p Scripts/python
cp ../$SCRIPT Scripts/python/$SCRIPT
rm ../$DOCUMENTO
zip -r ../$DOCUMENTO *
cd ..
rm -rf document_temp

Cuidado! Esse script apaga o documento original. O script acima é bem parecido com o que usamos e nunca tivemos problema, mas temos uma cópia dos documentos no SVN.

Segundo, monte a sua macro de forma que as mesmas rotinas possam ser executadas tanto como macro quanto conectando no OpenOffice por socket, de acordo com o ambiente em que rodam. Aqui nós usamos a seguinte classe abaixo, que ainda tem dois métodos que são uma mão na roda para dar uma resposta ao usuário ou converter uma data da planilha para o tipo datetime.datetime no Python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/ipython
#coding: utf-8
 
import uno
from datetime import datetime, timedelta
 
# http://codesnippets.services.openoffice.org/Office/Office.MessageBoxWithTheUNOBasedToolkit.snip
from com.sun.star.awt import WindowDescriptor
from com.sun.star.awt.WindowClass import MODALTOP
from com.sun.star.awt.VclWindowPeerAttribute import OK
 
class OODocument():
 
    @property
    def model(self):
        if __name__ != '__main__':
            return XSCRIPTCONTEXT.getDocument()
        else:
            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)
 
            return desktop.getCurrentComponent()
 
    def alert(self, msg, title = u'Atenção'):
        parentWin = self.model.CurrentController.Frame.ContainerWindow
 
        aDescriptor = WindowDescriptor()
	aDescriptor.Type = MODALTOP
	aDescriptor.WindowServiceName = 'messbox'
	aDescriptor.ParentIndex = -1
	aDescriptor.Parent = parentWin
	aDescriptor.WindowAttributes = OK
 
        tk = parentWin.getToolkit()
        box = tk.createWindow(aDescriptor)
 
        box.setMessageText(msg)
 
        if title:
            box.setCaptionText(title)
 
        box.execute()
 
    @property
    def basedate(self):
        """
        Este método é útil para operar datas, pois nas planilhas do OpenOffice as datas são representadas por
        inteiros. Some esse inteiro à esta data para obter a data real no python
        """
        return datetime(1899, 12, 30)
 
doc = OODocument()

O script acima pode ser o início da sua macro. Quando executado em linha de comando, se conectará a uma instância do OpenOffice aberta, ao terminar de testar sua rotina, empacote ela dentro do documento e faça um teste final.

Referências

Como colocar scripts python em macros:
http://wiki.services.openoffice.org/wiki/Python_as_a_macro_language

Boa fonte de documentação:
http://wiki.services.openoffice.org/wiki/Python

Como adicionar botões ao OpenOffice e associar a macros:
http://office-software.suite101.com/article.cfm/automating_openoffice_adding_buttons_to_calc

Classe python para inicializar um processo OO sem interface gráfica e controlá-lo pelo socket
http://www.linuxjournal.com/content/starting-stopping-and-connecting-openoffice-python

SEJA FELIZ! :-D


Comentários

  1. Vinicius
    29/12/2010 às 12:31 pm

    Muito bom mesmo o tutorial… faz um tempão que eu procurava por algo assim, simples e didático… vai ser de grande ajuda…
    Eu tava pensando numa coisa…
    É possível desenvolver as macros usando uma IDE e gravando as mesmas em D:\Documents and Settings\user\Dados de aplicativos\BrOffice.org\3\user\Scripts\python, testando as mesmas numa seção do BrOffice aberta? Pois pelo que percebi, não é necessário que o BrOffice seja reiniciado após cada alteração na macro…v daí eu embutiria a macro no documento APENAS após a mesma estar completamente pronta pra uso… se não fizer diferença nas chamadas do diretório do usuário e embutida no documento, é mais prático do que usar um script pra embutir o script no ods a cada edição do mesmo… ou mesmo usar um socket pra conexão…

    1. lfagundes
      06/01/2011 às 10:08 am

      Oi Vinicius,

      Essa é exatamente a idéia, desenvolver a macro usando sockets, semp recisar reiniciar, e só então embutir no documento no final. Estou desenvolvendo uma biblioteca para fazer isso, está em http://github.com/lfagundes/oosheet, você pode ser o primeiro alpha tester :-).

      asa

  2. [...] curtiu o tutorial de macros de OpenOffice.org em Python vai gostar do software que acabamos de publicar. Quem leu e não entendeu nada, vai gostar mais [...]

  3. Coelho
    14/08/2011 às 10:43 am

    lfagundes. bom dia.

    deixa eu ver se entendi, com o socket é possivel debugar para ver se ha algum erro?

    1. lfagundes
      16/08/2011 às 9:25 am

      sim, com socket é possível debugar. recomendo que veja a documentação do oosheet (http://oosheet.hacklab.com.br), biblioteca alto nível para criação de macros, em python.

      asa

Deixe seu comentário