Tutorial de macros em Python no OpenOffice.org
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



Vinicius
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…
lfagundes
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
OO, Sheet! Macros em Python finalmente fáceis « hacklab.com.br
[...] 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 [...]
Coelho
lfagundes. bom dia.
deixa eu ver se entendi, com o socket é possivel debugar para ver se ha algum erro?
lfagundes
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