4th Febbraio 2007

Programmare OpenOffice in Python

posted in python, openoffice, programmazione |

OpenOffice permette la programmazione di semplici macro per automatizzare compiti ripetitivi fino ad arrivare allo sviluppo di componenti complessi; tutto questo con i linguaggi più disparati, dall’OOoBasic (linguaggio che ricorda il VBA di Office) a Java e C++, o con linguaggi di scripting come Python e JavaScript. Vediamo come realizzare in Python una semplice macro per Writer che evidenzi in grassetto tutte le occorrenze di una parola selezionata dall’utente nel testo.

Il nostro sistema di riferimento sarà Kubuntu Edgy, ma i concetti espressi restano validi per tutte le distribuzioni GNU/Linux e anche per i sistemi Windows.

Assumendo che sul sistema siano già installati sia OpenOffice che Python, sarà sufficiente installare il pacchetto per lo sviluppo di componenti UNO in Python insieme ad eventuali dipendenze:

sudo apt-get install python-uno

Grazie a Python-UNO è possibile utilizzare lo scripting framework di OpenOffice, ma sfortunatamente non con l’ambiente di sviluppo integrato. E’ quindi necessario lavorare agli script Python con un editor a scelta e soprattutto piazzare gli script dove OpenOffice possa vederli. Attualmente sono disponibili 3 opzioni:

  1. nella home dell’utente e precisamente in ~/.openoffice.org2/user/Scripts/python
  2. nella share directory di OpenOffice, /usr/lib/openoffice/share/Scripts/python
  3. all’interno di un documento di OpenOffice

Sorvolando per brevità sulla terza opzione, possiamo indifferentemente scegliere una delle altre: in un caso la macro sarà disponibile solo per il possessore, nel secondo per tutti gli utenti del sistema.

Creiamo quindi lo script bolder.py, per esempio in ~/.openoffice.org2/user/Scripts/python.

# importa il simbolo per il testo in grassetto
from com.sun.star.awt.FontWeight import BOLD

def bolder():
	“”"Trasforma in grassetto tutte le occorrenze del testo selezionato”"”

	# ottieni il documento dallo script context
	xModel = XSCRIPTCONTEXT.getDocument()
	# recupera il controller
	xSelectionSupplier = xModel.getCurrentController()
	
	# ottieni l’elenco delle selezioni
	xIndexAccess = xSelectionSupplier.getSelection()
	
	# se l’utente ha selezionato del testo,
	# la selezione di interesse e’ la prima
	if xIndexAccess.getCount() > 0:
		xTextRange = xIndexAccess.getByIndex(0)
		# salva la parola selezionata
		selString = xTextRange.getString()
	
	# crea un descrittore per la ricerca delle parole
	if selString is not None:
		# istanzia un descrittore di ricerca
		search = xModel.createSearchDescriptor()
		# imposta la stringa da cercare
		search.SearchString = selString
		# imposta la ricerca di parole intere
		search.SearchWords = True
	
	# ciclo di modifica
	if search is not None:
		# trova la prima occorrenza
		found = xModel.findFirst(search)
		while found is not None:
			# imposta lo spessore dei caratteri sul testo trovato
			found.CharWeight = BOLD
			# cerca l’occorrenza successiva
			found = xModel.findNext(found.End, search)
			
	return None

Lo script consiste nella definizione di una funzione, bolder(). La docstring della funzione verrà visualizzata come descrizione della macro nell’apposito menu. Un’operazione preliminare è l’importazione del simbolo BOLD, necessario ad impostare il testo in grassetto.

All’interno della funzione la prima operazione da compiere è ottenere il documento, che nel gergo di OpenOffice è chiamato modello. XSCRIPTCONTEXT è un oggetto globale, definito per tutte le macro, che fornisce accesso al contesto di scripting. Invocandone il metodo getDocument() si ottiene l’handler al documento corrente.

Il documento è associato ad un controller. I controller sono componenti che conoscono e manipolano la presentazione corrente del documento. Il controller in questione implementa, fra le altre, l’interfaccia XSelectionSupplier: tramite questa interfaccia è possibile ottenere dal controller il testo selezionato dall’utente nel documento. In questo caso sarebbe stato possibile ottenere la selezione in modo più semplice invocando il metodo xModel.getCurrentSelection(), che restituisce la selezione corrente nel documento attivo.

Il metodo getSelection() di xSelectionSupplier fornisce l’indice delle selezioni; il perché di un indice anziché direttamente la selezione stessa sta nel fatto che l’utente può effettuare selezioni multiple. Per semplicità utilizziamo la prima selezione, ma solo dopo aver controllato che ne esista almeno una, con il metodo getCount(). Poiché la numerazione delle selezioni parte da 0, il metodo xIndexAccess.getByIndex(0) restituisce la prima selezione, in forma di un TextRange, il cui metodo getString() restituisce finalmente la parola selezionata.

Il passo successivo consiste nell’istanziare un descrittore di ricerca invocando il factory method createSearchDescriptor() del modello del documento, su cui vengono impostate la stringa di ricerca e la modalità per la ricerca di intere parole.

Non rimane che trovare le occorrenze della parola e renderle in grassetto. Il metodo findFirst() del modello trova la prima occorrenza del testo con le modalità definite nel descrittore. Sull’occorrenza trovata viene impostato un attributo, CharWeight, che è quello che determina il corpo del carattere: impostando CharWeight a BOLD raggiungiamo l’obiettivo, evidenziare in grassetto la parola. Il corpo del ciclo termina cercando l’occorrenza successiva, a partire dalla fine dell’occorrenza corrente, con il metodo findNext() del modello.

Il bolder nel menu Esegui Macro

Una volta salvato lo script si può avviare OpenOffice Writer e riempire in documento vuoto con del testo. Dopo aver selezionato una parola, dal menu principale scegliamo la voce “Strumenti/Macro/Esegui Macro…” dove troveremo fra le macro personali il nostro bolder pronto a trasformare in grassetto tutte le occorrenze della parola prescelta. Per semplificare l’esecuzione della macro possiamo assegnarla ad una combinazione di tasti, con “Strumenti/Personalizza…”.

Il binding della macro

Purtroppo appare immediatamente evidente quanto lo stile delle API di OpenOffice sia poco “pythonic”. Ciò è dovuto al fatto che le API Python in realtà sono semplicemente un binding alle API native di OpenOffice, per cui non abbiamo una lista Python di selezioni da cui ricavare la cardinalità con la funzione len() e da cui estrarre il primo elemento con l’indicizzazione diretta. Questo stile di programmazione è tuttavia circoscritto all’interazione diretta con il motore di OpenOffice e non è imposto ad eventuali classi sviluppate ad hoc, il che rende lo sviluppo di script complessi più rapido grazie alle feature del linguaggio Python.

Il vero problema è piuttosto una documentazione prolissa e poco chiara, soprattutto per quanto riguarda le API. Gli unici riferimenti per orientarsi nel mondo della programmazione di OpenOffice sono la Developer’s Guide e la IDL Reference.

There are currently 3 responses to “Programmare OpenOffice in Python”

Why not let us know what you think by adding your own comment! Your opinion is as valid as anyone elses, so come on... let us know what you think.

  1. 1 On Dicembre 5th, 2007, Claudio La Torre said:

    Salve.
    E’ ormai da settimane che provo a fare delle macro in Calc, usando python, ma invano. Le sarei molto grato se mi spiegasse come fare anche una semplice somma tra celle utilizzando python in Calc.
    In attesa di un Suo cordiale riscontro, porgo distinti saluti
    Claudio La Torre

  2. 2 On Dicembre 7th, 2007, leonardo said:

    Purtroppo la documentazione ufficiale di OpenOffice è quanto di meno chiaro possa esserci. Comunque questa macro dovrebbe funzionare, salvandola in sum.py nella giusta directory.

    def sum():
    “”"Demo calcoli sulle celle.”"”

    # ottiene il documento dallo script context
    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.getSheets().getByIndex(0)

    # inserisce 2 valori e una formula
    sheet.getCellByPosition(0, 0).setValue(10)
    sheet.getCellByPosition(0, 1).setValue(3)
    sheet.getCellByPosition(0, 2).setFormula(’=SUM(A1:A2)’)

    # legge 2 valori e mette la differenza in una cella
    v1 = sheet.getCellByPosition(0, 0).getValue()
    v2 = sheet.getCellByPosition(0, 1).getValue()
    result = v1 - v2
    sheet.getCellByPosition(1,0).setValue(result)

  3. 3 On Dicembre 9th, 2007, Claudio La Torre said:

    Grazie! Funziona. Adesso devo solo cercare di capire le logiche di implementazione.
    Mi sembra strano che, in questa macro, non sia stato necessario importare la biblioteca uno.
    Su questo sito ho trovato le varie API, disponibili in openoffice:
    http://api.openoffice.org/docs/common/ref/index-files/index-1.html
    Grazie ancora dell’aiuto.
    Claudio

Leave a Reply