Après avoir un peu exploré les possibilités offertes par les composants de type "add-in" pour Calc, et compte tenu de l'importante dispersion de l'information disponible (par ailleurs souvent obsolète), je vous propose ci-après un petit retour d'expérience en forme de tutoriel.
A. Qu'est-ce qu'un "add-in" ?
Un "add-in" est un type d'extension permettant l'intégration complète de fonctions personnalisées dans le module Calc. Un fonction ajoutée comme add-in est ainsi disponible depuis l'assistant de fonctions et expose ses paramètres dans la barre de formule, comme une fonction native.
Le tutoriel se propose ainsi d'ajouter deux fonctions de type texte :
- JOINDRE (plage [, séparateur]) pour concaténer le texte contenu dans une plage de cellule ;
- SCINDER (texte [, séparateur]) pour scinder un texte sur plusieurs cellules (fonction matricielle donc).
B. Prérequis
Une connaissance minimale du langage python et de la passerelle PyUno sera sans doute utile pour suivre la suite. De manière générale, une bonne connaissance de l'API d'OpenOffice est également nécessaire pour le développement d'une extension, quelle qu'elle soit.
L'installation du "software development kit" (SDK) est requise puisque celui-ci inclut les outils de développement nécessaires (utilitaires de compilation et fichiers de définition d'interfaces).
C. Avertissement
L'exemple développé dans ce tutoriel est écrit en python, un langage de script parfaitement adapté au développement d'extensions, mais un add-in peut évidemment être écrit dans n'importe quel langage supporté par OpenOffice (C++, Java...) pour autant qu'il autorise l'implémentation de classes ou d'objets nouveaux. Il n'est donc pas possible d'écrire une telle extension en basic.
La version de l'interpréteur python embarqué est différente entre OpenOffice (python 2) et LibreOffice (python 3). En outre, les dernières moutures de LibreOffice intègrent une version de PyUno légèrement différente. Il est important d'en être conscient si l'on souhaite développer une fonction compatible avec les deux suites.
Ce tutoriel est basé sur un environnement linux "classique" (Linux Mint) pour des raisons purement pratiques, entre autres l'accès aisé aux compilateurs et aux librairies requises. Mais il devrait pouvoir s'adapter sans trop de difficultés à d'autres systèmes d'exploitation.
D. Les composants d'un add-in
Un add-in est composé, comme une extension classique ("add-on"), d'une série de fichiers compressés dans un fichier zip portant l'extension ".oxt". Un add-in doit contenir au minimum les éléments suivants :
- un fichier binaire (extension .rdb) permettant d'ajouter à la base de registre la définition des fonctions personnelles ("interface") ;
- un fichier xml (extension .xcu) contenant les informations requises pour l'interface utilisateur : noms et descriptions des fonctions, noms et descriptions des paramètres, localisation, etc.
- le fichier d'implémentation proprement dit (extension ".py" en l'occurrence) ;
- un fichier description.xml contenant les éléments d'information relatifs à l'extension ;
- un sous-répertoire META-INF contenant un fichier manifest.xml, lequel permet au programme d'identifier les éléments précités.
1. Le fichier RDB
La création d'un fichier RDB nécessite deux étapes préalables : l'écriture d'un fichier de définition IDL et sa compilation dans un format intermédiaire URD.
1.a. Le fichier IDL
Les nouvelles fonctions doivent être définies au moyen d'un langage d'interface spécifique, nommé IDL.
Recopier le code ci-dessous et le sauver en tant que XTutoAddIn.idl :
Code : Tout sélectionner
#include <com/sun/star/uno/XInterface.idl>
module org { module forumfr { module TutoAddin {
interface XTutoAddIn
{
string pyjoin(
[in] sequence< sequence< string > > strings,
[in] any separator
);
sequence< sequence< string > > pysplit(
[in] string basestring,
[in] any separator
);
};
};};};
- L'instruction #include permet d'importer les interfaces utilisées par le fichier de définition. Un add-in requiert au minimum l'interface XInterface.
On trouve encore plusieurs sites expliquant que les interfaces XAddIn, XServiceName et optionnellement XCompatibilityNames sont également nécessaires. Ces informations sont aujourd'hui obsolètes : l'interface XAddIn n'est plus requise et les deux autres sont remplacées par le fichier de configuration XCU décrit plus loin. - La seconde ligne définit l'espace de nom propre à la nouvelle interface.
- La troisième ligne ouvre la définition de l'interface XTutoAddIn, qui comporte la "signature" des deux fonctions.
Chaque fonction est définie par son type et par son nom, suivis du type et du nom de chaque paramètre (ceux-ci précédés du code de direction [in] sur lequel il n'est pas utile de s'étendre ici).
- Les types disponibles pour un add-in sont décrits (en anglais) ici.
- Le type any renvoie notamment la valeur void ("vide", ou None en python) lorsque le paramètre n'est pas fourni : c'est donc ce type qu'il faut retenir pour un paramètre optionnel.
- Le type com.sun.star.table.XCellRange permet d'obtenir en argument, non les valeurs de la plage, mais l'objet uno CellRange correspondant, ce qui permet notamment d'accéder aux propriétés des cellules référencées.
- Le type com.sun.star.beans.XPropertySet permet d'obtenir en argument l'objet "document" contenant la formule, offrant donc l'accès aux propriétés de celui-ci. Ce type, qui ne peut être défini que pour un seul argument par fonction, est en outre implicite : il est "invisible" pour l'utilisateur, Calc se chargeant de fournir l'argument au moment d'évaluer la formule (l'extension numbertext par exemple utilise un argument de ce type pour accéder à la langue par défaut de l'interface).
Le fichier IDL est compilé en un fichier binaire URD grâce à la commande idlc, fournie avec le SDK. Exemple de syntaxe :
Code : Tout sélectionner
./idlc -I ../idl/ /chemin/vers/XTutoAddIn.idl
Le fichier URD est créé dans le même répertoire que le fichier IDL.
1.c. Compilation finale et fichier RDB
Le fichier URD peut enfin être transformé en un fichier RDB à l'aide de la commande regmerge (disponible désormais dans le répertoire d'installation d'OpenOffice) :
Code : Tout sélectionner
./regmerge /chemin/vers/XTutoAddIn.rdb /UCR /chemin/vers/XTutoAddIn.urd
2. Le fichier XCU
Le fichier XCU fournit au programme toutes les informations utiles pour l'affichage des noms de fonctions et de paramètres dans l'assistant de fonctions.
Recopier le code ci-dessous et le sauver en tant que TutoAddIn.xcu :
Code : Tout sélectionner
<?xml version="1.0" encoding="UTF-8"?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="CalcAddIns" oor:package="org.openoffice.Office">
<node oor:name="AddInInfo">
<node oor:name="org.forumfr.tutoriels.TutoAddin" oor:op="replace">
<node oor:name="AddInFunctions">
<node oor:name="pyjoin" oor:op="replace">
<prop oor:name="DisplayName">
<value xml:lang="en">JOIN</value>
<value xml:lang="fr">JOINDRE</value>
</prop>
<prop oor:name="Description">
<value xml:lang="en">Concatenate strings from the referred range with a given separator.</value>
<value xml:lang="fr">Concatène les textes de la plage source avec un séparateur donné.</value>
</prop>
<prop oor:name="Category"><value>Text</value></prop>
<node oor:name="Parameters">
<node oor:name="strings" oor:op="replace">
<prop oor:name="DisplayName">
<value xml:lang="en">range</value>
<value xml:lang="fr">plage</value>
</prop>
<prop oor:name="Description">
<value xml:lang="en">The range containing the strings to concatenate.</value>
<value xml:lang="fr">La plage contenant les textes à concaténer.</value>
</prop>
</node>
<node oor:name="separator" oor:op="replace">
<prop oor:name="DisplayName">
<value xml:lang="en">separator</value>
<value xml:lang="fr">séparateur</value>
</prop>
<prop oor:name="Description">
<value xml:lang="en">String used as separator (defaults to space).</value>
<value xml:lang="fr">Texte utilisé comme séparateur (espace par défaut).</value>
</prop>
</node>
</node>
</node>
<node oor:name="pysplit" oor:op="replace">
<prop oor:name="DisplayName">
<value xml:lang="en">SPLIT</value>
<value xml:lang="fr">SCINDER</value>
</prop>
<prop oor:name="Description">
<value xml:lang="en">Split a string with a given separator.</value>
<value xml:lang="fr">Scinde un texte avec un séparateur spécifique.</value>
</prop>
<prop oor:name="Category"><value>Text</value></prop>
<node oor:name="Parameters">
<node oor:name="basestring" oor:op="replace">
<prop oor:name="DisplayName">
<value xml:lang="en">string</value>
<value xml:lang="fr">texte</value>
</prop>
<prop oor:name="Description">
<value xml:lang="en">The string to be splitted.</value>
<value xml:lang="fr">Le texte à scinder.</value>
</prop>
</node>
<node oor:name="separator" oor:op="replace">
<prop oor:name="DisplayName">
<value xml:lang="en">separator</value>
<value xml:lang="fr">séparateur</value>
</prop>
<prop oor:name="Description">
<value xml:lang="en">String used as separator (defaults to space).</value>
<value xml:lang="fr">Texte utilisé comme séparateur (espace par défaut).</value>
</prop>
</node>
</node>
</node>
</node>
</node>
</node>
</oor:component-data>
Code : Tout sélectionner
<node oor:name="org.forumfr.tutoriels.TutoAddin" oor:op="replace">
Code : Tout sélectionner
<node oor:name="AddInFunctions">
<node oor:name="pyjoin" oor:op="replace">
[...]
</node>
<node oor:name="pysplit" oor:op="replace">
[...]
</node>
</node>
- DisplayName : le nom qui sera affiché dans Calc ;
- Description : la description utilisée par l'assistant de fonction ;
- Category : la catégorie utilisée par l'assistant de fonction. Les catégories disponibles sont (en anglais uniquement) : Database, Date&Time, Financial, Information, Logical, Mathematical, Matrix, Statistical, Spreadsheet, Text et Add-In.
Chaque nœud "fonction" possède également un nœud subordonné Parameters qui comprend la description de chacun des paramètres attendus :
Code : Tout sélectionner
<node oor:name="Parameters">
<node oor:name="strings" oor:op="replace">
[...]
</node>
<node oor:name="separator" oor:op="replace">
[...]
</node>
</node>
3. Le fichier d'implémentation
Recopier le code ci-dessous et le sauver en tant que TutoAddIn.py :
Code : Tout sélectionner
# -*- coding: utf-8 -*-
import unohelper
from itertools import chain
from com.sun.star.lang import IllegalArgumentException
from org.forumfr.TutoAddin import XTutoAddIn
class TutoAddIn(unohelper.Base, XTutoAddIn):
def __init__(self, ctx):
pass
def pyjoin(self, strings, separator):
sep = separator==None and " " or separator
s = tuple(chain.from_iterable(strings))
try:
return sep.join(s)
except AttributeError:
raise IllegalArgumentException(u'Second argument must be a string.', self, 2)
def pysplit(self, basestring, separator):
if separator == "":
return ((basestring,),)
else:
try:
r = basestring.split(separator)
return tuple(zip(r))
except TypeError:
raise IllegalArgumentException(u'Second argument must be a string.', self, 2)
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(TutoAddIn, "org.forumfr.tutoriels.TutoAddin", ())
- Il est indispensable d'importer le module unohelper, qui contient toutes les macros utilisées par la passerelle PyUno pour la création et l'enregistrement de l'extension, en particulier la classe ImplementationHelper.
- La fonction addImplementation de ImplementationHelper attend trois arguments :
- ‒ la ou les objets/classes implémentés par l'extension ;
‒ un nom d'implémentation unique (il ne doit pas entrer en conflit avec d'autres implémentations), qui doit correspondre au nom repris dans le fichier XCU décrit plus haut ;
‒ une liste des services implémentés (aucun dans le cas présent).
- ‒ la ou les objets/classes implémentés par l'extension ;
4. Le fichier description.xml
Recopier le code ci-dessous et le sauver en tant que description.xml :
Code : Tout sélectionner
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:d="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">
<version value="0.1" />
<identifier value="org.forumfr.tutoaddin" />
<dependencies>
<OpenOffice.org-minimal-version value="3.0" d:name="OpenOffice.org 3.0"/>
</dependencies>
<display-name>
<name lang="en">Two custom functions to illustrate Calc add-in how-to.</name>
<name lang="fr">Deux fonctions pour illustrer la création d'un add-in pour Calc.</name>
</display-name>
</description>
- Ce fichier est commun à toute extension et ses éléments sont décrits en détail dans le guide du développeur (explications en français ici).
- L'identifiant (<identifier value="org.forumfr.tutoaddin" />) est indépendant du nom d'implémentation décrit plus haut mais doit lui-même être unique : il permet d'identifier sans ambiguïté une extension, notamment dans le contexte de mises à jour.
5. Le fichier manifest.xml
Ce fichier est commun à toute extension et doit être placé, par convention, dans un sous-répertoire nommé META-INF. En déclarant chacun des composants de l'extension (nature et localisation), il permet au programme de les connaître.
Recopier le code ci-dessous et le sauver en tant que manifest.xml :
Code : Tout sélectionner
<manifest:manifest>
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=RDB"
manifest:full-path="XTutoAddIn.rdb"/>
<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-data"
manifest:full-path="TutoAddIn.xcu"/>
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=Python"
manifest:full-path="TutoAddIn.py"/>
</manifest:manifest>
- La déclaration du fichier description.xml n'est pas requise. La liste des fichiers à déclarer et la manière de le faire sont détaillées dans le guide du développeur.
E. Création de l'extension proprement dite
- Sélectionner l'ensemble de fichiers créés plus haut (en respectant l'arborescence) et les compresser au format zip ;
- renommer le fichier obtenu en TutoAddIn.oxt ;
- installer.
Pour s'assurer que l'intégration à Calc est complète, il est important de relancer le programme immédiatement après l'installation.
F. Utilisation avec basic
Il est également possible d'utiliser une fonction ajoutée par add-in dans une macro basic :
‒ soit en instanciant l'extension comme un service
Code : Tout sélectionner
sub main
addin = createUnoService("org.forumfr.tutoriels.TutoAddin")
noms = addin.pysplit("Saint-Remy-en-Bouzemont-Saint-Genest-et-Isson","-")
for each row in noms
for each col in row
print col
next col
next row
end sub
Code : Tout sélectionner
sub main
fs = createUnoService("com.sun.star.sheet.FunctionAccess")
func = "org.forumfr.tutoriels.TutoAddin.pysplit"
noms = fs.callFunction(func, array("Saint-Remy-en-Bouzemont-Saint-Genest-et-Isson","-"))
for each row in noms
for each col in row
print col
next col
next row
end sub
G. Conclusion
Si vous avez lu ce tutoriel jusqu'ici, les éléments indispensables à la construction d'un add-in n'ont donc plus de secret pour vous .
Il est évident toutefois qu'expliquer avec toute la clarté nécessaire un processus comme celui-ci n'est pas aisé, et dépend beaucoup des a priori que chacun peut avoir sur les éléments d'information allant de soi. Vos commentaires et questions sont donc les bienvenus.
Ajout : 14.10.2016 Petite modification dans le fichier TutoAddIn.py |