[Python] (bien) débuter avec LibreOffice ou OpenOffice

Venez ici afin d'enrichir la documentation de nos suites bureautiques préférées. Déposez une demande ou y répondre par la création ou la traduction d'un tutoriel.

Modérateur: Vilains modOOs

[Python] (bien) débuter avec LibreOffice ou OpenOffice

Messagepar jeanmi2403 » 06 Déc 2016 17:18

Bonjour,
Lorsque j’ai voulu commencer à programmer en Python, c’était pour étudier une extension écrite dans ce langage, et qui ne fonctionnait plus.
J’ai donc commencé par apprendre un peu le langage, et ça s’est plutôt bien passé.
J’ai ensuite essayé de faire interagir des scripts avec Libre Office, et c’est là que les difficultés ont vraiment commencé.
Je compte faire ici le récit de mes galères et de mes succès (en fait, peu de choses de mon cru, mais des solutions et des réponses trouvées un peu partout) en espérant que cela puisse servir à d’autres, et éviter qu’ils ne renoncent en raison de difficultés qui semblent rédhibitoires et qui trouvent toujours solution.

Je tiens à remercier les collègues du forum sans qui j’aurais mis beaucoup plus longtemps pour avancer.

Aborder un ,nouveau langage de programmation n’est pas trop difficile tant qu’il s’agit de :
  • syntaxe, mots-clés
  • structures de contrôle
Ce qui est plus délicat :
  • les entrées sorties (ou la communication avec l’utilisateur)
  • la bibliothèque d’objets, de méthodes et de fonctions
  • Avec LibO et AoO, l’interaction avec les documents (ce qui est le but des macros, oui ?)
Pour commencer, je me suis attaqué à de « petits » sujets, et j’ai fait à chaque fois un exemple dans un document en attachant le script à un bouton, de manière à pouvoir tester rapidement le fonctionnement, sans passer par tous les menus Outis/Macros/Gérer les macros/Python/ etc.
Ce sont ces documents-exemples que je vais partager ici.
Ce sont des solutions qui fonctionnent, pas nécessairement les meilleures, ni les plus optimisées.
Sommaire
Où sont les scripts ?
Où est l'éditeur de textes ?
Des ressources et des cours
Caractères accentués, indentation, des causes d'échec !
Accès aux documents : le contexte de script
Les exemple disponibles dans LibreOffice
Les boîtes de message
Utiliser une bibliothèque d'outils
Utiliser une bibliothèque d'outils dans un document
Boîtes de dialogue
Boîte de dialogue d'accès aux fichier
Dernière édition par jeanmi2403 le 07 Déc 2016 17:09, édité 6 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python] Où sont les scripts ?

Messagepar jeanmi2403 » 06 Déc 2016 17:26

C’est la première question que l’on se pose en voulant utiliser python dans LibO
Les fichiers/scripts peuvent se trouver à trois endroits différents.
Avec LibreOffice
Dans le sous-dossier LibreOffice du dossier personnel de l’utilisateur ;
    C:\Users\<nom utilisateur>\AppData\Roaming\LibreOffice\4\user\Scripts\Python
Le dossier C:\Users\<nom utilisateur>\AppData\Roaming\LibreOffice\4\user est créé à la première utilisation de Libre Office par l’utilisateur. Il faudra créer soi-même le dossier Scripts et le sous-dossier python.

Dans le sous-dossier python du dossier du logiciel
  • C:\Program Files\LibreOffice 5\share\Scripts\python pour une version x64,
  • C:\Program Files (x86)\LibreOffice 5\share\Scripts\python pour une version x32
Dans le dossier python d’un document.

Personnellement je ne travaille que dans le dossier personnel. Une fois le script opérationnel, je le mets dans un document, s’il ne doit fonctionner que dans ce cadre, ou bien pour le distribuer (comme en Basic…) et jamais dans le dossier du logiciel.
Pour accéder rapidement à ce dossier, un raccourci sur le bureau facilite les choses.
Avec Apache OpenOffice
Dans le sous-dossier OpenOffice du dossier personnel de l’utilisateur
    C:\Users\<nom utilisateur>\AppData\Roaming\OpenOffice\4\user\Scripts\Python.
Le dossier C:\Users\<nom utilisateur>\AppData\Roaming\LibreOffice\4\user\Scripts est créé à la première utilisation de Open Office par l’utilisateur. Il faudra créer soi-même le sous-dossier python.
Dans le sous-dossier python du dossier du logiciel
    C:\Program Files (x86)\OpenOffice 4\share\Scripts\python, AoO est 32 bits.
Dans le dossier python d’un document.

4 : Les développeurs ont décidé de ne pas créer un nouveau dossier à chaque changement de version majeure (4/5/etc) avec les problèmes de migration de profil qui surviennent parfois.
Comme cela a commencé avec la version 5.0.0, le dossier utilisateur reste figé à 4. (Gérard24)
Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:02, édité 1 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python] Où est l’éditeur de textes ?

Messagepar jeanmi2403 » 06 Déc 2016 17:42

Réponse simple, il n’y en a pas. Personnellement, j’utilise NOTEPAD++ qui met en évidence la syntaxe python et permet l’auto-complétion des mots et des fonctions. Il permet aussi le contrôle visuel de l’indentation, en refermant/développant les structures de contrôle.
Il suffira d’associer les fichiers *.py à notepad++
Il faudra choisir la version 32bits, la 64bits (Version 7.2) ne prenant pas en charge l’association de fichiers.
La coloration n'est active que si le fichier a l'extension "py". Il faut donc que le fichier soit enregistré au moins une fois.
Image-Notepad.png
Exemple de coloration avec Notepad

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:05, édité 2 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python]Ressources et cours

Messagepar jeanmi2403 » 06 Déc 2016 18:00

Où trouver de l'aide et de la documentation. Liste non exhaustive !
Documentation python
La référence absolue : python.org :
Cours
OpenClassrooms (anciennement le site du zéro)
Developpez.com (choisir programmation puis tutoriels python)
Apprendre Python
Programmation orientée interface sous LibreOffice/OpenOffice : Automatiser les tâches avec python
Libre Office/Open Office
Python comme langage macro

Transfert de BASIC à PYTHON (A lire impérativement même si on n'a pas programmé en OObasic)
LibreOffice 5.3 SDK (pauvre en python, un seul exemple)
PyUno (Python et OpenOffice)
Bibliothèques
PyooCalc (Créer des documents calc, des rapports)
UNO et API
The Apache OpenOffice API Project
Passerelle Python-UNO
SDK API reference LibreOffice
SDK API référence OpenOffice
Outils
Analyseur de syntaxe en ligne
Sommaire
Dernière édition par jeanmi2403 le 16 Déc 2016 19:31, édité 5 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python] caractères accentués, indentation : des causes d'éc

Messagepar jeanmi2403 » 06 Déc 2016 18:19

Caractères accentués
Mon premier travail a été de tester les exemples fournis avec LibreOffice (et d’autres glanés çà et là).
Pour bien comprendre leur fonctionnement, j’ai traduit ou complété les commentaires, évidemment avec des caractères accentués.
La surprise c’est qu’ensuite les fichiers sont affichés dans l’interface de gestion des scripts, mais on ne voit aucune des fonctions à l’intérieur, bien entendu pas question de tester ou d’exécuter quoi que ce soit, le bouton Exécuter est grisé.
L’outil de gestion des macros python d’Hubert Lambert renvoie un message d’erreur relativement compréhensible.
Le souci vient de ce que python ne comprend pas nativement ces caractères, mais uniquement ASCII7 bits. Il faut convertir le fichier en UTF-8, et commencer le fichier avec l’instruction suivante :
Code : Tout sélectionner   AgrandirRéduire
# -*- coding: utf-8 -*-

L'indentation
Le même phénomène peut se produire dans le cas d’une erreur d’indentation.
Cette erreur peut -être très sournoise ! En modifiant un script écrit par quelqu'un d'autre, récupéré sur le forum, dans une documentation, l'indentation sera réalisée avec des espaces (souvent trois). Un saut de ligne dans Notepad++ introduira une tabulation et l'indentation deviendra incohérente pour l'interpréteur (un seul caractère au lieu de 3).
Dans ce cas, l’extension de Hubert Lambert donne un message d’erreur explicite.
Si on souhaite que les macros soient portables sur AoO (prise en compte des caractères accentués à l’affichage sans écrire de vermicelle à la place), il faudra rajouter la ligne :
Code : Tout sélectionner   AgrandirRéduire
from __future__ import unicode_literals

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:06, édité 1 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

Re: [Python] Le contexte de script

Messagepar jeanmi2403 » 06 Déc 2016 18:38

Références :
AoO : http://www.openoffice.org/api/docs/comm ... ntext.html
LibO : http://api.libreoffice.org/docs/idl/ref ... ntext.html
En macro Python, on doit utiliser la variable XSCRIPTCONTEXT, qui fournit aux scripts une interface, et des moyens d’accès aux diverses interfaces qui permettent d’agir sur les documents.
Plusieurs méthodes disponibles, entre autres :
  • getDocument() récupère l’objet document courant (interface de type xModel)
  • getDesktop() récupère une instance du service Desktop
  • getComponentContext() récupère le contexte du composant courant
Agir sur les documents
Après avoir récupéré le document model=XSCRIPTCONTEXT.getDocument(), on pourra déterminer le type de document, avec la fonction Python hasattr()
  • if hasattr(model, "Text") vrai pour un document Writer
  • if hasattr(model, "Sheets") vrai pour un document Calc
  • if hasattr(model, "Presentation »"), etc..
Pour un document texte, on récupère une interface XtextRange, à laquelle sont attachées un certain nombre de méthodes, dont
  • getText() renvoie l’interface Xtext permettant l’accès au contenu
  • getStart() début du texte
  • getEnd() fin du texte
  • getString() récupère le contenu de la chaîne
  • setString() pose le contenu de la chaîne
Exemple :
Code : Tout sélectionner   AgrandirRéduire
# python permet un code compact sans utiliser de variables intermédiaires
XSCRIPTCONTEXT.getDocument().getText().setString("Bonjour, j'ai écrasé tout le contenu entier du document !")

réalise exactement ce que dit le message !
Exemple-BonjourEcrase.odt
L'exemple ci-dessus, dans un fichier texte
(13.53 Kio) Téléchargé 34 fois

 Ajout : Attention, les exemples ne fonctionnent que s'ils sont enregistrés, sinon les macros sont bloquées... 

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:08, édité 2 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python] Les exemples disponibles

Messagepar jeanmi2403 » 06 Déc 2016 18:52

On trouvera dans le dossier C:\Program Files\LibreOffice 5\share\Scripts\python des exemples de scripts
  • HelloWorld.py
  • Capitalise.py
  • TableSample (dans le dossier pythonSamples)
On peut tester ces scripts depuis l’interface de LibreOffice on trouvera ici d’autres exemples :
Plutôt que le traditionnel "Hello world", je propose un exemple avec deux procédures qui affichent la version de python et la localisation de l’exécutable, dans un document Writer et un document Calc. Ce qui permet d'utiliser deux paramètres de la bibliothèque sys.
Capitalise agit sur le contenu du texte pour basculer les textes sélectionnés en majuscules et minuscules
TableSample construit un tableau dans un document vierge.

En Python, il faut définir une fonction Python avec un argument (ou une liste variable d’arguments de longueur variable) pour prendre l’argument passé lorsque la fonction est exécutée via le bouton de la barre d'outils assignée ou un bouton d’une boîte de dialogue, sinon, on obtient un message d'erreur
Exemple :
Hello() takes 0 positional arguments but 1 was given

La fonction doit être déclarée comme recevant une liste d’arguments :
Code : Tout sélectionner   AgrandirRéduire
def hello(*args) :

Exemple-VersionPython.odt
(15.79 Kio) Téléchargé 42 fois

Exemple-Capitalise.odt
(22 Kio) Téléchargé 27 fois

Exemple-TableSample.odt
(17.52 Kio) Téléchargé 29 fois

Pour lire les scripts, les extraire du document en rajoutant l'extension zip, ou bien installer l'extension apso de Hubert Lambert
Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 17:15, édité 2 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

Re: [Python] Les boîtes de message

Messagepar jeanmi2403 » 06 Déc 2016 18:57

Les boîtes de message sont destinées à créer des interactions courtes avec l’utilisateur. En retour la fonction renvoie un nombre correspondant au bouton choisi.
On utilise les constantes de la bibliothèque plutôt que les valeurs numériques, plus difficiles à mémoriser.
L’icône est déterminée par le type de boîte.
Les icônes sont différentes, selon qu’on est avec LibO ou Ao0
Références
Une boîte générique :
On a très souvent besoin d’une boîte simple, avec le seul bouton OK. La fonction suivante utilise les trois derniers paramètres de manière optionnelle (merci Hubert)
Code : Tout sélectionner   AgrandirRéduire
import uno
from com.sun.star.awt.MessageBoxType import MESSAGEBOX, INFOBOX, ERRORBOX, WARNINGBOX, QUERYBOX
from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK
#createUnoService n’existe pas en Python...
def createUnoService(service, ctx):
    return ctx.ServiceManager.createInstanceWithContext(service, ctx)

def MsgBox(message, titre="Message", boxtype='message', boutons=BUTTONS_OK):
    ctx = uno.getComponentContext()
    win = createUnoService("com.sun.star.frame.Desktop", ctx).getCurrentFrame().ContainerWindow
    types = {'message': MESSAGEBOX, 'info': INFOBOX, 'error': ERRORBOX,
             'warn': WARNINGBOX, 'query': QUERYBOX}
    tk = createUnoService("com.sun.star.awt.Toolkit", ctx)
    box = tk.createMessageBox(win, types[boxtype], boutons, titre, message)
    return box.execute()

Exemple-MsgBox.odt
Exemples de boîtes MsgBox pour tester les différents paramètres
(20.67 Kio) Téléchargé 26 fois

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:10, édité 1 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

Re: [Python] Bibliothèque d’outils

Messagepar jeanmi2403 » 06 Déc 2016 19:04

La boîte de message MsgBox(), et l’outil Xray sont typiques du besoin. Dès qu’un programme en cours de développement a besoin de donner de l’information où d’examiner des objets, il faut que cela puisse se faire rapidement, sans avoir à copier un bout de code dans le module en cours de test.
Il faut donc pouvoir appeler une fonction depuis un autre module que celui où elle se trouve.
En Basic
il suffit d’une instruction BasicLibraries.LoadLibrary("NomDuModule") pour que les fonctions de ce module soient accessibles.
Par exemple :
Code : Tout sélectionner   AgrandirRéduire
BasicLibraries.LoadLibrary("XrayTool") 'dans une fonction, et l’assigner à l’événement « Démarrer l’application »

En Python
il suffit de placer le module dans un dossier « pythonpath » dans Scripts\python (ce dossier est invisible dans le gestionnaire de macros)
et d’ajouter l’instruction import « NomDuModule ». Les fonctions sont ensuite accessibles en préfixant leur nom par « NomDuModule »
Exemple :
Code : Tout sélectionner   AgrandirRéduire
# le module bibliothèque s’appelle Outils.py
import Outils
# pour appeler la fonction
Outils.MsgBox(<le message>)

Un détail : cela fonctionne si le premier import se fait depuis un module situé au même niveau que le dossier « pythonpath » (le dossier est rajouté au PATH système) mais pas depuis un autre dossier. En revanche, tout fonctionne correctement ensuite.

Pour ajouter le chemin, s’il n’est pas encore pris en compte :

Code : Tout sélectionner   AgrandirRéduire
   try:
        import Outils
    except ImportError:
        import sys
        pythonpath = r"C:\Users\<utilisateur>\AppData\Roaming\LibreOffice\4\user\Scripts\python\pythonpath"
        sys.path.append(pythonpath)
        importOutils

On serait tenté d’assigner cette instruction au démarrage de l’application, mais il y déjà une fonction Basic (pour le chargement automatique de Xray)!
Et comme il ne semble pas possible de modifier l’environnement Python depuis un script Basic….
Dans ce cas, on appelle la fonction Python depuis la fonction Basic

Code : Tout sélectionner   AgrandirRéduire
‘ fonction Basic assignée au démarrage de l’application
Sub LoadingLibraries
Dim scriptPro As Object
BasicLibraries.LoadLibrary("XrayTool") ‘Ca c’est pour Xray
‘et ça c’est pour lancer le script Python
scriptPro =  createUnoService("com.sun.star.script.provider.MasterScriptProvider")
oScript =  ScriptPro.GetScript("vnd.sun.star.script:InitPython.py$SetPath?language=Python&location=user")
oScript.invoke(Array(), Array(), Array() )
End Sub

Et en python
Code : Tout sélectionner   AgrandirRéduire
#Fonction SetPath, dans le fichier InitPython.py
def SetPath(*args):
    import sys
    pythonpath = "C:\\Users\\<utilisateur>\AppData\\Roaming\\LibreOffice\\4\\user\Scripts\\python\\pythonpath"
    sys.path.append(pythonpath)
    return None

Exemple-Bibli-MsgBox.odt
Test bibliothèque
(19.95 Kio) Téléchargé 27 fois

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:11, édité 9 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

Re: [Python] Bibliothèque dans un document

Messagepar jeanmi2403 » 06 Déc 2016 19:11

On peut avoir à transmettre un document contenant aussi la bibliothèque (ou construire un programme en plusieurs modules), plutôt que d’intégrer les outils dans le script.
Pour cela, dans le script, (en supposant que la bibliothèque est dans le fichier Outils.py) insérer le code suivant,

Code : Tout sélectionner   AgrandirRéduire
import sys
import unohelper

if not 'Outils' in sys.modules:
    doc = XSCRIPTCONTEXT.getDocument()
    url = unohelper.fileUrlToSystemPath('{}/{}'.format(doc.URL,'Scripts/python’))
    sys.path.insert(0, url)
import Outils

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:11, édité 1 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

Re: [Python] Boîte de saisie

Messagepar jeanmi2403 » 06 Déc 2016 19:18

Pour une communication plus aboutie, on peut avoir à demander une réponse autrement que par un bouton :lol:
Il est possible de construire les boîtes de dialogue intégralement par script.
Il est également possible d’utiliser les boîtes de dialogue créées avec l’outil de gestion (Outils/Macros/Gérer les boîtes de dialogue).
Le code préliminaire :
Code : Tout sélectionner   AgrandirRéduire
    model = XSCRIPTCONTEXT.getDocument()
    smgr = uno.getComponentContext().ServiceManager
    dp = smgr.createInstanceWithArguments("com.sun.star.awt.DialogProvider",(model,) )

Céation de la boîte :
Code : Tout sélectionner   AgrandirRéduire
   # en supposant que la boîte DialogInput.xdl se trouve dans le document
   dialog = dp.createDialog("vnd.sun.star.script:Standard.DialogInput?location=document")

Dans le cas d’une boîte de dialogue dans le profil utilisateur ou dans le dossier du logicel.:
Code : Tout sélectionner   AgrandirRéduire
    ctx = XSCRIPTCONTEXT.getComponentContext()
    smgr = ctx.getServiceManager()
    dp = smgr.createInstanceWithContext("com.sun.star.awt.DialogProvider", ctx)
    dialog = dp.createDialog("vnd.sun.star.script:Standard.DialogUser?location=application")

Il faudra manipuler quelques fichiers de configuration pour faire prendre en compte des boîtes de dialogue placées dans le dossier logiciel de LibreOffice.
Pour les déplacer, utiliser Importer/exporter du menu de gestion.
Exemple-DialogueEntree.odt
Boîte de saisie
(20.91 Kio) Téléchargé 33 fois

Sommaire
Dernière édition par jeanmi2403 le 07 Déc 2016 16:12, édité 1 fois.
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python] Boîte de dialogue d'accès aux fichiers

Messagepar jeanmi2403 » 06 Déc 2016 19:31

Le contrôle Fichier des boîtes de dialogus standard est un peu limité. Il faudrait en plus construire un parcours de répertoires avec le contrôle Tree.
Le service compétent pour des boîtes de dialogue complètes est le service FilePicker :
Le service FilePicker décrit dans l’API présente un manque sous Windows, la méthode setDisplayDirectory n’agit pas, il faut utiliser OfficeFilePicker, qui possède les mêmes propriétés et méthodes.
Sur le document , on trouvera deux boutons :
Ouvrir
Définit le dossier du document comme dossier pour l’affichage.
Présente une boîte de dialogue Ouvrir, avec un filtre à deux composants, odt et ods.
Renvoie le nom du fichier choisi. Ne charge pas le fichier.
Enregistrer
Définit le dossier du document comme dossier pour l’affichage.
Présente une boîte de dialogue Ouvrir, avec un nom de fichier par défaut, un filtre à trois composants, odt, ods et odp, et la case à cocher pour l’auto extension du nom de fichier.
Filepicker prévient si l’on choisit un fichier existant.
Renvoie le nom de fichier choisi, avec l’extension choisie dans la liste déroulante.
Remarque importante
Ces deux scripts ne font rien que renvoyer le numéro du type de bouton choisi.
Ils ne chargent rien et n’enregistrent rien ! On peut donc les exécuter jusqu’au bout sans crainte….
Exemple-FilePicker.odt
(22.15 Kio) Téléchargé 33 fois

Sommaire
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

[Python] Guide du Programmeur Python

Messagepar LibreOfficiant » 05 Jan 2017 11:48

Bonjour à tous

Je m'apprête à complémenter/rédiger en français, en anglais un Guide du Programmeur Python ( sur la base de mon parcours du combattant ma toute petite expérience en la matière ) en mettant à jour le Wiki de LibreOffice ainsi que ce forum. Son contenu devrait être pour commencer:

o Documentations (API,forums, ..)
o Installation
o Modélisation (UML)
o Outils (extensions..)
o Mise au Point (IDEs)
o Examples multilangages (Basic, Python, Javascript, ..)
o Ecriture d'extensions

Je privilégie et utilise tout ce qui peut être gratuit, de préférence 'Open Source' et multi-plateforme. Ainsi à ce jour mes outils sont StarUML. PyCharm, LibreOffice, VirtualBox, GNU/Linux.

Je ne souhaite pas limiter mon contenu aux seuls outils que je connais ou pratique. Ainsi je serai heureux que vous m'indiquiez quels sont les vôtres et surtout pourquoi vous les préférez à d'autres. Les liens sur les forums français ou anglais sont les bienvenus.

Merci par avance

cf. https://wiki.documentfoundation.org/Mac ... n_Guide/fr
libO 5.3 32bit sur Win7 x64 & Win10 x64 | libO 5.2 sur Ubuntu 16.10 Yakkety Yak x64 | aOOo 4.2.6.3 sur Mint 17.1 x64
Boîte à Outils Python: StarUML, Geany, PyCharm et bien sûr Apso, MRI, xRay, Object Inspector..
https://wiki.documentfoundation.org/Macros/Design_Guide/fr
LibreOfficiant
NOOuvel adepte
NOOuvel adepte
 
Message(s) : 18
Inscrit le : 03 Jan 2017 15:54

Re: [Python] Guide du Programmeur Python

Messagepar Bidouille » 05 Jan 2017 11:52

Bonjour,

Question déplacée dans la bonne section et fusionnée avec un fil sur le même sujet.
Avatar de l’utilisateur
Bidouille
RespOOnsable forum
RespOOnsable forum
 
Message(s) : 9273
Inscrit le : 08 Nov 2005 18:23
Localisation : Brest, France

Re: [Python] (bien) débuter avec LibreOffice ou OpenOffice

Messagepar jeanmi2403 » 05 Jan 2017 17:22

Bonjour,
J'aimerais autant que ce fil reste "propre" , puisqu'il ne contient que des exemples d'utilisation.
Cela dit, je serais ravi de participer à cette documentation, puisque avec Libreofficiant, nous intervenons aux mêmes endroits des wiki Aoo et LibO
Cordialement,
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

Re: [Python] (bien) débuter avec LibreOffice ou OpenOffice

Messagepar Bidouille » 06 Jan 2017 10:11

jeanmi2403 a écrit:J'aimerais autant que ce fil reste "propre" , puisqu'il ne contient que des exemples d'utilisation.

Le fil peut très bien être enrichi par les participations d'autres utilisateurs.
C'est le principe de cette section.
Une fois que vous estimez le travail terminé, il sera "lavé" des messages inutiles puis placé dans la section Tutoriels.
Avatar de l’utilisateur
Bidouille
RespOOnsable forum
RespOOnsable forum
 
Message(s) : 9273
Inscrit le : 08 Nov 2005 18:23
Localisation : Brest, France

Re: [Python] (bien) débuter avec LibreOffice ou OpenOffice

Messagepar jeanmi2403 » 06 Jan 2017 12:24

Bonjour,
Bidouille a écrit:Le fil peut très bien être enrichi par les participations d'autres utilisateurs.
C'est le principe de cette section.
Une fois que vous estimez le travail terminé, il sera "lavé" des messages inutiles puis placé dans la section Tutoriels.

Complètement d'accord avec ça.
Merci,
Jean-Michel
LibO 5.1.6 et AoO 4.1.3
Windows 7 Familiale Premium SP1 x64 et Windows 10 x64
Avatar de l’utilisateur
jeanmi2403
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 461
Inscrit le : 18 Jan 2008 11:02
Localisation : Sucy en Brie

PropertyValue : heurs et malheurs

Messagepar Hubert Lambert » 07 Jan 2017 23:10

 
Heurs
Il est très simple d'instancier un objet struct à partir de python, en particulier le très fréquent com.sun.star.beans.PropertyValue.
Un exemple parmi d'autres :
Code : Tout sélectionner   AgrandirRéduire
def createPropertyValues(**kwargs):
    from com.sun.star.beans import PropertyValue
    return tuple(PropertyValue(k,0,kwargs[k],0) for k in kwargs)

qui permet de passer intuitivement les paires nom/valeur en tant qu'arguments nommés, comme dans cet exemple :
Code : Tout sélectionner   AgrandirRéduire
    props = createPropertyValues(ActiveConnection=oConnection, OpenMode=sMode)


Malheurs
Dans certains cas particulier, notamment lorsqu'on doit passer une tableau de PropertyValue à une méthode du type replaceByName() ou replaceByIndex(), l'instruction suivante coincera :
Code : Tout sélectionner   AgrandirRéduire
    # affecter une macro à l'événement "vue créée" du document courant
    doc = XSCRIPTCONTEXT.getDocument()
    events = doc.Events
    script = "vnd.sun.star.script:Standard.Module1.mamacro?language=Basic&location=document"
    eventtype = "Script"
    event = createPropertyValues(Script=script, EventType=eventtype)
    events.replaceByName("OnViewCreated", event)    #--> IllegalArgumentException

Le problème (et sa solution) est évoqué et expliqué à plusieurs endroits sur cette page.
La cause se trouve dans le typage contextuel ("duck typing") propre à python, qui dans de rares cas est incapable de transmettre le type attendu par le programme. Le contournement consiste à invoquer la fonction en passant, à l'aide de la classe Any du module uno, un objet précisant le type et la valeur du paramètre. Dans l'exemple ci-dessus, la dernière ligne devra ainsi être remplacée par celle-ci :
Code : Tout sélectionner   AgrandirRéduire
    uno.invoke(events, "replaceByName", ("OnViewCreated", uno.Any("[]com.sun.star.beans.PropertyValue", event)))    #--> ok


Un détail encore
Si, comme moi, par commodité ou pour une meilleure compatibilité avec LibreOffice, vous avez pris l'habitude de forcer les chaînes de caractères en unicode sous OpenOffice avec la ligne d'import
Code : Tout sélectionner   AgrandirRéduire
from __future__ import unicode_literals

le contournement ci-dessus renverra une erreur de type RuntimeException : le fonction uno.invoke attend en effet un chaîne normale en second paramètre, et la chaîne unicode est rejetée. Il faudra donc la transformer à l'aide de la fonction str :
Code : Tout sélectionner   AgrandirRéduire
    uno.invoke(events,str("replaceByName"), ("OnViewCreated",uno.Any("[]com.sun.star.beans.PropertyValue", event)))
AOOo 4.1.2 sur Win7 | LibreOffice 5.x sur divers systèmes Linux
--
| « Nos défauts devraient nous donner une qualité : l'indulgence pour les défauts des autres » (Rivarol)
Avatar de l’utilisateur
Hubert Lambert
Membre enthOOusiaste
Membre enthOOusiaste
 
Message(s) : 476
Inscrit le : 06 Avr 2016 09:26

Re: [Python] (bien) débuter avec LibreOffice ou OpenOffice

Messagepar LibreOfficiant » 11 Jan 2017 14:38

A propos des boîtes de message ci-dessus, on peut ajouter:
Code : Tout sélectionner   AgrandirRéduire
from com.sun.star.awt.MessageBoxResults import CANCEL as ANNULER, OK, YES as OUI, NO as NON, RETRY as RÉESSAYER, IGNORE as IGNORER

http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1MessageBoxResults.html

Cordialement
libO 5.3 32bit sur Win7 x64 & Win10 x64 | libO 5.2 sur Ubuntu 16.10 Yakkety Yak x64 | aOOo 4.2.6.3 sur Mint 17.1 x64
Boîte à Outils Python: StarUML, Geany, PyCharm et bien sûr Apso, MRI, xRay, Object Inspector..
https://wiki.documentfoundation.org/Macros/Design_Guide/fr
LibreOfficiant
NOOuvel adepte
NOOuvel adepte
 
Message(s) : 18
Inscrit le : 03 Jan 2017 15:54

[Python] XSCRIPTCONTEXT revisité

Messagepar LibreOfficiant » 13 Jan 2017 11:18

Comme le savent les programmeurs non Basic (sic), *Office greffe à leur macros une variable globale XSCRIPTCONTEXT, véritable point d'entrée aux fonctions de l'API. Charge ensuite à eux de coder, de mettre au point dans *Office mais sans filet sinon le célèbre xRay, ou bien hors *Office via un (EDI) Environnement de Développement Intégré (re-sic). User d'un EDI signifie passer par la passerelle Python et donc du code supplémentaire avant de pouvoir utiliser les API.

Je simplifie tout cela au sein d'un module que j'utilise comme suit afin de faciliter mes validations futures au sein d' *Office :
Code : Tout sélectionner   AgrandirRéduire
from Office.Bridge import XSCRIPTCONTEXT  # Please comment this line when running within (Libre|Open)Office


J'insère cette ligne pour tester mes macros hors *Office. J'inhibe ou ôte cette instruction pour mes tests fonctionnels dans *Office.

Pré-requis : Le module 'Bridge.py' tente de se connecter à deux instances différentes d' *Office, une visible une cachée, lancées comme suit :
Code : Tout sélectionner   AgrandirRéduire
"C:\Program Files (x86)\LibreOffice 5\program\soffice.exe" --accept="pipe,name=LibreOffice;urp;"
"C:\Program Files (x86)\LibreOffice 5\program\soffice.exe" --accept="socket,host=localhost,port=2017;urp;" --norestore --nologo --headless --nodefault

S'il échoue il lance une exception signalant qu'il ne trouve pas ces instances d' *Office. Autrement il instancie un singleton nommé XSCRIPTCONTEXT supportant l'interface com.sun.star.script.provider.XScriptContext. Il suffit ensuite de recourir aux méthodes courantes de XSCRIPTCONTEXT :
  • XSCRIPTCONTEXT.getComponentContext()
  • XSCRIPTCONTEXT.getDesktop()
  • XSCRIPTCONTEXT.getDocument()
Usage : Il est possible de se connecter à d'autres sessions d' *Office de cette manière :
Code : Tout sélectionner   AgrandirRéduire
XSCRIPTCONTEXT.connect(pipe="OpenOffice")  # returns object supporting com.sun.star.uno.XComponentContext interface
XSCRIPTCONTEXT.connect(host='localhost', port=1515)



Le code du module 'Bridge.py' est le suivant :
Code : Tout sélectionner   AgrandirRéduire
""" Module header

Module name : Bridge
Purpose     : Substitute to XSCRIPTCONTEXT when running Python scripts outside of (Libre|Open)Office.
Author      : Alain H. Romedenne
Description : The XSCRIPTCONTEXT object facilitates connections to running instances of (Libre|Open)Office.
    It connects to a piped instance of (Libre|Open)Office named 'LibreOffice'
    It connects to a localhost instance of (Libre|Open)Office using port=yyyy (yyyy=current year)
    It can connect to any variation of pipe name or port# used by a started (Libre|Open)Office.
    .ComponentContext, .CurrentDocument, .Desktop properties can be used.
    .connect(..): obj, .createInstance(name: str), .createUNOService(service: str) methods are available.
Intended    : Enable SSH connections to remote instances of (Libre|Open)Office.

Usage       :
    from Office.Bridge import XSCRIPTCONTEXT  # Please comment this line when running within (Libre|Open)Office

Credits     :
    christopher5106.github.io/office/2015/12/06/openoffice-libreoffice-automate-your-office-tasks-with-python-macros.html
    http://www.linuxjournal.com/content/starting-stopping-and-connecting-openoffice-python
    pyoo (c) 2014 Seznam.cz, a.s.

"""

#region Imports, constants

import datetime
import sys
# import socket  # only needed on win32-OOo3.0.0

#region UNO imports
import uno
# UNO interfaces
from com.sun.star.script.provider import XScriptContext                     # Script Context
from com.sun.star.frame import XModel                                       # - Current document object
from com.sun.star.document import XScriptInvocationContext                  # - Invocation dependent object
from com.sun.star.frame import XDesktop                                     # - Desktop object
from com.sun.star.uno import XComponentContext                              # - Component object
# UNO Exceptions
from com.sun.star.connection import NoConnectException
#from com.sun.star.container import NoSuchElementException

_NoConnectException = uno.getClass('com.sun.star.connection.NoConnectException')
# We try to catch them and to re-throw Python standard exceptions, if running outside of (Libre|Open)Office

#endregion

_VERBOSE = False
_MY_OFFICE = 'LibreOffice'
_MY_HOST = 'localhost'
_MY_PORT = datetime.date.today().year
_MY_PIPE = _MY_OFFICE
NOCONNECT_EXCEPTION = 'Failed to connect to %s on host=%s,port=%d,pipe=%s'
NODESKTOP_EXCEPTION = 'Failed to create % desktop on host=%s,port=%d,pipe=%s'
_UNSUPPORTED_PLATFORM_MSG = "'%s' platform is not supported yet."
_NOSTARTUP_EXCEPTION_MSG = 'Failed to start %s on host=%s,port=%d,pipe=%s'

#endregion

if _VERBOSE: print(datetime.date.today())

#region Object-Oriented Code
class Singleton(type):
    """
    A Singleton design pattern
    Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly
    """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]
        #else:  # Remove comments if you expect explicit coding rejection.
        #   raise Exception("An instance of that object already exists.")

class _OfficeSession(XScriptContext, object, metaclass=Singleton,):
    """
    This class substitutes XSCRIPTCONTEXT when running Python scripts outside (Libre|Open)Office
    Usage:
    from LibreOffice import XSCRIPTCONTEXT  #  Please comment this line when running within (Libre|Open)Office
    """
    def __init__(self):  # Class constructor
        _OfficeSession.connect(hostname=_MY_HOST, port=_MY_PORT)
        _OfficeSession.connect(pipe=_MY_PIPE)
    #region Properties
    @property
    def ComponentContext(self) -> XComponentContext :
        """
        Connects to a running listening 'LibreOffice' IPC instance.
        :return: XSCRIPTCONTEXT substitute
        """
        return _OfficeSession._sessions[_MY_PIPE]
    @property
    def CurrentDocument(self) -> XModel :
        return self.getDocument() #  Office current base/IDE/calc/draw/impress/math/writer document Or 'None'
    @property
    def Desktop(self) -> XDesktop :
        return self.createInstance("com.sun.star.frame.Desktop")
    def InvocationContext(self) -> XScriptInvocationContext :
        return None
    #endregion

    #region Methods
    _sessions = {}  # (Libre|Open)Office listening IPC instances identified by pipe name or port #
    @staticmethod
    def connect(hostname=_MY_HOST, port=_MY_PORT, pipe=None) -> XComponentContext :
        """
        Connects to a running listening instance of (Libre|Open)Office
        e.g. ctx = obj.connect(port=2016) connects to 'started LibreOffice on port#2016' throws exception otherwise
        When specified pipe argument takes precedence over hostname:port arguments pair
        :param hostname: defaults to 'localhost'
        :param port: defaults to this year e.g. 2016
        :param pipe: .. 'LibreOffice' ..
        :return: last established context: defaults to 'LibreOffice' piped visible instance
        """
        local_context = uno.getComponentContext()
        resolver = local_context.ServiceManager.createInstanceWithContext(
            'com.sun.star.bridge.UnoUrlResolver', local_context)
        try:  # conn = 'pipe,name=%s' % pipe if pipe: else 'socket,host=%s,port=%d' % (hostname, port)
            if pipe:
                conn = 'pipe,name=%s' % pipe
            else:
                conn = 'socket,host=%s,port=%d' % (hostname, port)
            connection_url = 'uno:%s;urp;StarOffice.ComponentContext' % conn
            if _VERBOSE: print(connection_url)
            _established_context = resolver.resolve(connection_url)
        except NoConnectException:  # thrown when LibreOffice specified instance isn't started
            tb = sys.exc_info()[2]
            raise Exception(NOCONNECT_EXCEPTION % (_MY_OFFICE, hostname, port, pipe)).with_traceback(tb) \
                from _NoConnectException
        if pipe:
            _key = pipe
        elif port:
            _key = port
        else:
            _key = _MY_PORT
        _OfficeSession._sessions[_key] = _established_context
        if _VERBOSE: print(__name__ + ' (yyyy-mmm-dd hh:mm:ss) Connection to %s established with %s' % (_MY_OFFICE, conn))
        return _established_context
    def createInstance(self, name):
        _remote_context = self.ComponentContext
        obj = _remote_context.ServiceManager.createInstanceWithContext(name, _remote_context)
        return obj
    def createUNOService(self, name):  # as offered in Office Basic
        return self.createInstance(name)
    def getComponentContext(self):
        return self.ComponentContext
    def getDesktop(self):
        return self.Desktop
    def getDocument(self):
        return self.Desktop.CurrentComponent
    #endregion
#endregion

XSCRIPTCONTEXT = _OfficeSession()

#region Functions
def createObject(class_name: str):
    """
    LibreOffice class objects factory.
    Substitute to LibreOffice Basic CreateUnoService(), equivalent to OLE/COM createObject()...
    Throws uno.RuntimeException
    :param class_name: e.g. 'com.sun.star.sdb.DatabaseContext'
    :return: a LibreOffice instance of 'class_name'.
    """
    ctx = XSCRIPTCONTEXT.getComponentContext()
    return ctx.getServiceManager().createInstanceWithContext(class_name, ctx)

def CreateObject(class_name: str):
    """
    LibreOffice class objects factory.
    Substitute to LibreOffice Basic CreateUnoService(), equivalent to OLE/COM createObject()...
    Throws uno.RuntimeException
    :param class_name: e.g. 'com.sun.star.sdb.DatabaseContext'
    :return: a LibreOffice instance of 'class_name'.
    """
    ctx = uno.getComponentContext()
    return ctx.getServiceManager().createInstanceWithContext(class_name, ctx)

#endregion


""" Change Log aka Summary of changes

Date        Full name           Description                                                                     Ref. #
2016-Dec-12 Alain H. Romedenne  Added createObject() public function in place of <session>.createXXX() methods
2016-Oct-15 Alain H. Romedenne  Module creation                                                                 ... ...

"""

# Quick n Dirty Testing
if __name__ == '__main__':

    ctx = XSCRIPTCONTEXT.ComponentContext
    dsk = XSCRIPTCONTEXT.getDesktop()
    doc = XSCRIPTCONTEXT.getDocument()  # May return None
    obj = createObject("com.sun.star.frame.Desktop")
    print(ctx)
    print(dsk)  # com.sun.star.uno.XInterface
    print(doc)  # com.sun.star.lang.XComponent - base, calc, draw, basicIDE, impress, math, writer, ..

    #  print(XSCRIPTCONTEXT.ComponentContext.ImplementationName)
    print(dsk.ImplementationName)  #  com.sun.star.comp.framework.Desktop
    if doc is not None:
        print(doc.ImplementationName)  # com.sun.star.comp.*



Bonus: Voici les tests unitaires 'Bridge_UnitTests.py' que j'ai cru bon devoir écrire pour valider ce module :
Code : Tout sélectionner   AgrandirRéduire
import unittest

from Office.Bridge import XSCRIPTCONTEXT as XSC

VERBOSE = True
_DESKTOP = 'com.sun.star.comp.framework.Desktop'
_PIPE_NAME = 'LibreOffice'

class TestSessionProperties(unittest.TestCase):
    def setUp(self):
        self.y = XSC
    def tearDown(self):
        del self.y

    def test_constructor(self):
        print(XSC.ComponentContext)
        print(XSC.Desktop)  # com.sun.star.uno.XInterface
        print(XSC.CurrentDocument)  # com.sun.star.lang.XComponent - base, calc, draw, impress, math, writer, ..
    def test_ComponentContext(self):
        ctx = XSC.ComponentContext
        #self.assertIsInstance(ctx, ('com.sun.star.uno.XInterface'))
        self.assertEqual(XSC.ComponentContext, XSC.getComponentContext())
    def test_Desktop(self):
        #self.assertTrue(LibreOffice.Desktop.SupportedServices=='com.sun.star.frame.Desktop')
        self.assertTrue(XSC.getDesktop().ImplementationName == _DESKTOP)
        self.assertEqual(XSC.Desktop, XSC.getDesktop())
    def test_doc(self):
        if not XSC.getDocument() == None:
            self.assertTrue(XSC.CurrentDocument.ImplementationName ==
                            XSC.getDocument().ImplementationName)
        else:
            self.assertTrue(XSC.CurrentDocument == XSC.getDocument())
        self.assertEqual(XSC.CurrentDocument, XSC.getDocument())
    def test_InvocationContext(self):
        #self.assertTrue( XSC.InvocationContext is None)
        pass

class TestSessionMethods(unittest.TestCase):
    def test_connect(self):
        _xsc = XSC.connect(port=2016)
        _xsc = XSC.connect(pipe=_PIPE_NAME)

class TestSessionEvents(unittest.TestCase):
    pass

"""
class TestExceptions(TestCase):

    def setUp(self):
        src._DEBUG = True

    def test_Exceptions(self):
        self.assertRaises(self, src._NoConnectException, x.connect('localhost', 445))
        self.assertRaises(self, src._NoConnectException, x.connect(pipe='aPipename'))
        self.assertRaises(self, src._NoConnectException, x.connect(pipe='LibreOffice'))
"""

if __name__ == '__main__':

    unittest.main()



J'ai testé celà avec LibreOffice 5 sous Windows. Celà doit fonctionner aussi avec OpenOffice ainsi que sur d'autres plates-formes.
Usez-en, abusez-en .. et dîtes moi ce que vous en pensez !

Cordialement
libO 5.3 32bit sur Win7 x64 & Win10 x64 | libO 5.2 sur Ubuntu 16.10 Yakkety Yak x64 | aOOo 4.2.6.3 sur Mint 17.1 x64
Boîte à Outils Python: StarUML, Geany, PyCharm et bien sûr Apso, MRI, xRay, Object Inspector..
https://wiki.documentfoundation.org/Macros/Design_Guide/fr
LibreOfficiant
NOOuvel adepte
NOOuvel adepte
 
Message(s) : 18
Inscrit le : 03 Jan 2017 15:54

Re: [Python] (bien) débuter avec LibreOffice ou OpenOffice

Messagepar LibreOfficiant » 14 Jan 2017 19:54

A propos de Bibliothèques d'outils ci-dessus,

Il est possible d'appeler xRay et Mri depuis un script python.
xRay interrompra le cours la macro, mais pas Mri d'après mes tests.

On peut bien sûr recourir à *.createMessageBox, mais ces 2 outils sont tellement supérieurs.

Il faudrait (que je) traduire la documentation (en anglais) de Mri.

Cordialement
libO 5.3 32bit sur Win7 x64 & Win10 x64 | libO 5.2 sur Ubuntu 16.10 Yakkety Yak x64 | aOOo 4.2.6.3 sur Mint 17.1 x64
Boîte à Outils Python: StarUML, Geany, PyCharm et bien sûr Apso, MRI, xRay, Object Inspector..
https://wiki.documentfoundation.org/Macros/Design_Guide/fr
LibreOfficiant
NOOuvel adepte
NOOuvel adepte
 
Message(s) : 18
Inscrit le : 03 Jan 2017 15:54

[Python] XSCRIPCONTEXT revisité

Messagepar LibreOfficiant » 16 Fév 2017 15:26

Comme le savent les programmeurs non Basic, *Office greffe à leur macros une variable globale XSCRIPTCONTEXT, véritable point d'entrée aux fonctions de l'API. Charge ensuite à eux de coder, de mettre au point dans *Office mais sans filet sinon le célèbre xRay, ou bien hors *Office via un (EDI) Environnement de Développement Intégré (sic). User d'un EDI Python hors *Office signifie passer par la passerelle Python et donc du code supplémentaire avant de pouvoir utiliser les API au complet. J'utilise indiféremment Geany ou PyCharm dans un tel contexte.

Je simplifie tout cela au sein d'un module que j'utilise comme suit afin de faciliter mes développement dans un IDE externe i.e. hors *Office :
Code : Tout sélectionner   AgrandirRéduire
from Office.Bridge import XSCRIPTCONTEXT  # Please comment this line when running within (Libre|Open)Office

J'insère cette ligne pour tester mes macros hors *Office. J'inhibe ou ôte cette instruction pour mes tests fonctionnels dans *Office.

Pré-requis : Le module 'Bridge.py' tente de se connecter à deux instances différentes d' *Office, une visible une cachée, lancées comme suit sous Windows :
Code : Tout sélectionner   AgrandirRéduire
"C:\Program Files (x86)\LibreOffice 5\program\soffice.exe" --accept="pipe,name=LibreOffice;urp;"
"C:\Program Files (x86)\LibreOffice 5\program\soffice.exe" --accept="socket,host=localhost,port=2017;urp;" --norestore --nologo --headless --nodefault

S'il échoue il lance une exception signalant qu'il ne trouve pas ces instances d' *Office. Autrement il instancie un singleton nommé XSCRIPTCONTEXT supportant l'interface com.sun.star.script.provider.XScriptContext. Il suffit ensuite de recourir aux méthodes courantes de XSCRIPTCONTEXT :
  • XSCRIPTCONTEXT.getComponentContext()
  • XSCRIPTCONTEXT.getDesktop()
  • XSCRIPTCONTEXT.getDocument()
Usage : Il est possible de se connecter à d'autres sessions d' *Office de cette manière :
Code : Tout sélectionner   AgrandirRéduire
XSCRIPTCONTEXT.connect(pipe="OpenOffice")  # returns object supporting com.sun.star.uno.XComponentContext interface
XSCRIPTCONTEXT.connect(host='localhost', port=1515)

Le code du module 'Bridge.py' est le suivant :
Code : Tout sélectionner   AgrandirRéduire
""" Module header

Module name : Bridge
Purpose     : Substitute to XSCRIPTCONTEXT when running Python scripts outside of (Libre|Open)Office.
Author      : Alain H. Romedenne
Description : The XSCRIPTCONTEXT object facilitates connections to running instances of (Libre|Open)Office.
    It connects to a piped instance of (Libre|Open)Office named 'LibreOffice'
    It connects to a localhost instance of (Libre|Open)Office using port=yyyy (yyyy=current year)
    It can connect to any variation of pipe name or port# used by a started (Libre|Open)Office.
    .ComponentContext, .CurrentDocument, .Desktop properties can be used.
    .connect(..): obj, .createInstance(name: str), .createUNOService(service: str) methods are available.
Intended    : Enable SSH connections to remote instances of (Libre|Open)Office.

Usage       :
    from Office.Bridge import XSCRIPTCONTEXT  # Please comment this line when running within (Libre|Open)Office

Credits     :
    christopher5106.github.io/office/2015/12/06/openoffice-libreoffice-automate-your-office-tasks-with-python-macros.html
    http://www.linuxjournal.com/content/starting-stopping-and-connecting-openoffice-python
    pyoo (c) 2014 Seznam.cz, a.s.

"""

#region Imports, constants

import datetime
import sys
# import socket  # only needed on win32-OOo3.0.0

#region UNO imports
import uno
# UNO interfaces
from com.sun.star.script.provider import XScriptContext                     # Script Context
from com.sun.star.frame import XModel                                       # - Current document object
from com.sun.star.document import XScriptInvocationContext                  # - Invocation dependent object
from com.sun.star.frame import XDesktop                                     # - Desktop object
from com.sun.star.uno import XComponentContext                              # - Component object
# UNO Exceptions
from com.sun.star.connection import NoConnectException
#from com.sun.star.container import NoSuchElementException

_NoConnectException = uno.getClass('com.sun.star.connection.NoConnectException')
# We try to catch them and to re-throw Python standard exceptions, if running outside of (Libre|Open)Office

#endregion

_VERBOSE = False
_MY_OFFICE = 'LibreOffice'
_MY_HOST = 'localhost'
_MY_PORT = datetime.date.today().year
_MY_PIPE = _MY_OFFICE
NOCONNECT_EXCEPTION = 'Failed to connect to %s on host=%s,port=%d,pipe=%s'
NODESKTOP_EXCEPTION = 'Failed to create % desktop on host=%s,port=%d,pipe=%s'
_UNSUPPORTED_PLATFORM_MSG = "'%s' platform is not supported yet."
_NOSTARTUP_EXCEPTION_MSG = 'Failed to start %s on host=%s,port=%d,pipe=%s'

#endregion

if _VERBOSE: print(datetime.date.today())

#region Object-Oriented Code
class Singleton(type):
    """
    A Singleton design pattern
    Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly
    """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]
        #else:  # Remove comments if you expect explicit coding rejection.
        #   raise Exception("An instance of that object already exists.")

class _OfficeSession(XScriptContext, object, metaclass=Singleton,):
    """
    This class substitutes XSCRIPTCONTEXT when running Python scripts outside (Libre|Open)Office
    Usage:
    from LibreOffice import XSCRIPTCONTEXT  #  Please comment this line when running within (Libre|Open)Office
    """
    def __init__(self):  # Class constructor
        _OfficeSession.connect(hostname=_MY_HOST, port=_MY_PORT)
        _OfficeSession.connect(pipe=_MY_PIPE)
    #region Properties
    @property
    def ComponentContext(self) -> XComponentContext :
        """
        Connects to a running listening 'LibreOffice' IPC instance.
        :return: XSCRIPTCONTEXT substitute
        """
        return _OfficeSession._sessions[_MY_PIPE]
    @property
    def CurrentDocument(self) -> XModel :
        return self.getDocument() #  Office current base/IDE/calc/draw/impress/math/writer document Or 'None'
    @property
    def Desktop(self) -> XDesktop :
        return self.createInstance("com.sun.star.frame.Desktop")
    def InvocationContext(self) -> XScriptInvocationContext :
        return None
    #endregion

    #region Methods
    _sessions = {}  # (Libre|Open)Office listening IPC instances identified by pipe name or port #
    @staticmethod
    def connect(hostname=_MY_HOST, port=_MY_PORT, pipe=None) -> XComponentContext :
        """
        Connects to a running listening instance of (Libre|Open)Office
        e.g. ctx = obj.connect(port=2016) connects to 'started LibreOffice on port#2016' throws exception otherwise
        When specified pipe argument takes precedence over hostname:port arguments pair
        :param hostname: defaults to 'localhost'
        :param port: defaults to this year e.g. 2016
        :param pipe: .. 'LibreOffice' ..
        :return: last established context: defaults to 'LibreOffice' piped visible instance
        """
        local_context = uno.getComponentContext()
        resolver = local_context.ServiceManager.createInstanceWithContext(
            'com.sun.star.bridge.UnoUrlResolver', local_context)
        try:  # conn = 'pipe,name=%s' % pipe if pipe: else 'socket,host=%s,port=%d' % (hostname, port)
            if pipe:
                conn = 'pipe,name=%s' % pipe
            else:
                conn = 'socket,host=%s,port=%d' % (hostname, port)
            connection_url = 'uno:%s;urp;StarOffice.ComponentContext' % conn
            if _VERBOSE: print(connection_url)
            _established_context = resolver.resolve(connection_url)
        except NoConnectException:  # thrown when LibreOffice specified instance isn't started
            tb = sys.exc_info()[2]
            raise Exception(NOCONNECT_EXCEPTION % (_MY_OFFICE, hostname, port, pipe)).with_traceback(tb) \
                from _NoConnectException
        if pipe:
            _key = pipe
        elif port:
            _key = port
        else:
            _key = _MY_PORT
        _OfficeSession._sessions[_key] = _established_context
        if _VERBOSE: print(__name__ + ' (yyyy-mmm-dd hh:mm:ss) Connection to %s established with %s' % (_MY_OFFICE, conn))
        return _established_context
    def createInstance(self, name):
        _remote_context = self.ComponentContext
        obj = _remote_context.ServiceManager.createInstanceWithContext(name, _remote_context)
        return obj
    def createUNOService(self, name):  # as offered in Office Basic
        return self.createInstance(name)
    def getComponentContext(self):
        return self.ComponentContext
    def getDesktop(self):
        return self.Desktop
    def getDocument(self):
        return self.Desktop.CurrentComponent
    #endregion
#endregion

XSCRIPTCONTEXT = _OfficeSession()

#region Functions
def createObject(class_name: str):
    """
    LibreOffice class objects factory.
    Substitute to LibreOffice Basic CreateUnoService(), equivalent to OLE/COM createObject()...
    Throws uno.RuntimeException
    :param class_name: e.g. 'com.sun.star.sdb.DatabaseContext'
    :return: a LibreOffice instance of 'class_name'.
    """
    ctx = XSCRIPTCONTEXT.getComponentContext()
    return ctx.getServiceManager().createInstanceWithContext(class_name, ctx)

def CreateObject(class_name: str):
    """
    LibreOffice class objects factory.
    Substitute to LibreOffice Basic CreateUnoService(), equivalent to OLE/COM createObject()...
    Throws uno.RuntimeException
    :param class_name: e.g. 'com.sun.star.sdb.DatabaseContext'
    :return: a LibreOffice instance of 'class_name'.
    """
    ctx = uno.getComponentContext()
    return ctx.getServiceManager().createInstanceWithContext(class_name, ctx)

#endregion


""" Change Log aka Summary of changes

Date        Full name           Description                                                                     Ref. #
2016-Dec-12 Alain H. Romedenne  Added createObject() public function in place of <session>.createXXX() methods
2016-Oct-15 Alain H. Romedenne  Module creation                                                                 ... ...

"""

# Quick n Dirty Testing
if __name__ == '__main__':

    ctx = XSCRIPTCONTEXT.ComponentContext
    dsk = XSCRIPTCONTEXT.getDesktop()
    doc = XSCRIPTCONTEXT.getDocument()  # May return None
    obj = createObject("com.sun.star.frame.Desktop")
    print(ctx)
    print(dsk)  # com.sun.star.uno.XInterface
    print(doc)  # com.sun.star.lang.XComponent - base, calc, draw, basicIDE, impress, math, writer, ..

    #  print(XSCRIPTCONTEXT.ComponentContext.ImplementationName)
    print(dsk.ImplementationName)  #  com.sun.star.comp.framework.Desktop
    if doc is not None:
        print(doc.ImplementationName)  # com.sun.star.comp.*


Bonus: Voici les tests unitaires 'Bridge_UnitTests.py' que j'ai cru bon devoir écrire pour valider ce module :
Code : Tout sélectionner   AgrandirRéduire
import unittest

from Office.Bridge import XSCRIPTCONTEXT as XSC

VERBOSE = True
_DESKTOP = 'com.sun.star.comp.framework.Desktop'
_PIPE_NAME = 'LibreOffice'

class TestSessionProperties(unittest.TestCase):
    def setUp(self):
        self.y = XSC
    def tearDown(self):
        del self.y

    def test_constructor(self):
        print(XSC.ComponentContext)
        print(XSC.Desktop)  # com.sun.star.uno.XInterface
        print(XSC.CurrentDocument)  # com.sun.star.lang.XComponent - base, calc, draw, impress, math, writer, ..
    def test_ComponentContext(self):
        ctx = XSC.ComponentContext
        #self.assertIsInstance(ctx, ('com.sun.star.uno.XInterface'))
        self.assertEqual(XSC.ComponentContext, XSC.getComponentContext())
    def test_Desktop(self):
        #self.assertTrue(LibreOffice.Desktop.SupportedServices=='com.sun.star.frame.Desktop')
        self.assertTrue(XSC.getDesktop().ImplementationName == _DESKTOP)
        self.assertEqual(XSC.Desktop, XSC.getDesktop())
    def test_doc(self):
        if not XSC.getDocument() == None:
            self.assertTrue(XSC.CurrentDocument.ImplementationName ==
                            XSC.getDocument().ImplementationName)
        else:
            self.assertTrue(XSC.CurrentDocument == XSC.getDocument())
        self.assertEqual(XSC.CurrentDocument, XSC.getDocument())
    def test_InvocationContext(self):
        #self.assertTrue( XSC.InvocationContext is None)
        pass

class TestSessionMethods(unittest.TestCase):
    def test_connect(self):
        _xsc = XSC.connect(port=2016)
        _xsc = XSC.connect(pipe=_PIPE_NAME)

class TestSessionEvents(unittest.TestCase):
    pass

"""
class TestExceptions(TestCase):

    def setUp(self):
        src._DEBUG = True

    def test_Exceptions(self):
        self.assertRaises(self, src._NoConnectException, x.connect('localhost', 445))
        self.assertRaises(self, src._NoConnectException, x.connect(pipe='aPipename'))
        self.assertRaises(self, src._NoConnectException, x.connect(pipe='LibreOffice'))
"""

if __name__ == '__main__':

    unittest.main()


J'ai testé celà avec LibreOffice 5.x sous Windows en utilisant soit Geany, soit PyCharm. Celà doit fonctionner aussi avec OpenOffice ainsi que sur d'autres plates-formes.
Usez-en, abusez-en .. et dîtes moi ce que vous en pensez !

Cordialement
Dernière édition par LibreOfficiant le 22 Mars 2017 19:55, édité 1 fois.
libO 5.3 32bit sur Win7 x64 & Win10 x64 | libO 5.2 sur Ubuntu 16.10 Yakkety Yak x64 | aOOo 4.2.6.3 sur Mint 17.1 x64
Boîte à Outils Python: StarUML, Geany, PyCharm et bien sûr Apso, MRI, xRay, Object Inspector..
https://wiki.documentfoundation.org/Macros/Design_Guide/fr
LibreOfficiant
NOOuvel adepte
NOOuvel adepte
 
Message(s) : 18
Inscrit le : 03 Jan 2017 15:54

Re: Appel de routines Basic depuis Python

Messagepar LibreOfficiant » 18 Fév 2017 19:19

Préambule
LibreOffice permet le développement de macros en Basic, en Beanshell, en Javascript ou en Python. Leur exécution dans un document s’effectue sans considération du langage. Il est possible d’appeler une routine écrite en Basic depuis une fonction écrite en Python. La routine Basic doit impérativement être soit personnelle, soit partagée, elle ne doit pas être stockée dans un document. La passerelle pyuno doit être présente.
Appels Basic depuis Python
La manière d’appeler xRay, écrit en Basic, depuis Python fournit la base de nos exemples. Cet appel ne retourne aucune valeur :
Code : Tout sélectionner   AgrandirRéduire
import uno
from com.sun.star.uno import RuntimeException as _rtex
def xray(myObject):
  try:
    sm = uno.getComponentContext().ServiceManager
    mspf = sm.createInstanceWithContext("com.sun.star.script.provider.MasterScriptProviderFactory", uno.getComponentContext())
    scriptPro = mspf.createScriptProvider("")
    xScript = scriptPro.getScript("vnd.sun.star.script:XrayTool._Main.Xray?language=Basic&location=application")
    xScript.invoke((myObject,), (), ())
    return
  except:
     raise _rtex("\nBasic library Xray is not installed", uno.getComponentContext())

L’appel de fonctions Basic comme MsgBox ou InputBox depuis Python, et l’exploitation de leurs résultats est possible. Les exemples qui suivent l’illustrent de manière volontairement simplifiée. Les macros en pièce jointe détaillent ces exemples plus précisement, en incluant par exemple une gestion des erreurs.
Deux bibliothèques _Python et _Basic contiennent chacune un module appelé devTools. Chaque module est organisé comme suit :
  • _Python / devTools
    • getScript ( .. ): ..script.provider.XScript
    • MessageBox( .. ): int
    • MsgBox( .. ): int
    • InputBox( .. ): str
    • macros exemples
  • _Basic /devTools
    • MessageBox( .. ) As Integer
    • _MsgBox( .. ) As Integer
    • _InputBox(.. ) As String
    • macros exemples
Les macros en gras appellent leur homologues en Basic ou en Python.
Il est nécessaire d’exporter la librairie _Basic comme personnelle ou partagée pour lancer les macros exemples en Python.
Les macros exemples Python s’exécutent depuis le menu Outils – Macros… - Exécuter la Macro
Py2Bas.Figure.jpg
Dialogue d'appel de routines Basic depuis Python

Si la librairie _Python n’apparaît pas sous Unix, il convient d’installer la passerelle pyuno comme suit :
Code : Tout sélectionner   AgrandirRéduire
sudo apt-get install libreoffice-script-provider-python

Appel de MsgBox et InputBox depuis Python
Les programmes Basic ..
Code : Tout sélectionner   AgrandirRéduire
Private Function _MsgBox( msg As String, Optional options As Integer, Optional title As String ) As Integer
   _MsgBox = MsgBox( msg, options, title )
End Function
Private Function _InputBox( prompt As String, Optional title As String, Optional defaultInput As String) As String
   _InputBox = InputBox( prompt, title, defaultInput )
End Function

.. sont appelés en Python comme suit :
Code : Tout sélectionner   AgrandirRéduire
def MsgBox(message: str, type_buttons_default=0, title='LibreOffice') -> int:  #
   xScript = _getScript("_MsgBox")
   res = xScript.invoke((message,type_buttons_default,title), (), ())
   return res[0]
def InputBox(prompt: str, title='LibreOffice': str, defaultValue='': str):
   xScript = _getScript("_InputBox")
   res = xScript.invoke((prompt,title,defaultValue), (), ())
   return res[0]

L’attribut Private indique une fonction interne à ne pas utiliser en Basic, il n’a pas d’effet. Le caractère préfixe souligné (_) pratiqué en Python traduit la même convention de codage, il est également informatif. Les paramètres facultatifs en Basic sont gérés depuis Python à l’aide d’arguments nommés valorisés aux valeurs par défaut :
  • types_buttons_default=0
  • title='LibreOffice'
  • defaultValue=''
Une fonction getScript() est responsable de la localisation et du chargement mémoire des scripts Basic :
Code : Tout sélectionner   AgrandirRéduire
from com.sun.star.script.provider import XScript
def getScript(script: str, library='_Basic', module='devTools') -> XScript:
   sm = uno.getComponentContext().ServiceManager
   mspf = sm.createInstanceWithContext("com.sun.star.script.provider.MasterScriptProviderFactory", uno.getComponentContext())
   scriptPro = mspf.createScriptProvider("")
   scriptName = "vnd.sun.star.script:"+library+"."+module+"."+script+"?language=Basic&location=application"
   xScript = scriptPro.getScript(scriptName)
   return xScript

Convention d’appel Python vers Basic
La documentation de l’interface com.sun.star.script.provider.Xscript du SDK nous fournit la description de trois tuples paramètres d’appel de la méthode invoke() dans cet ordre :
  1. aParams (i.e. *args en Python) représente les paramètres passés positionnellement
  2. aOutParamIndex représente les indices des paramètres modifiés suite à l’appel
  3. aOutParam représente le(s) résultat(s) de l’appel
Les conventions d’appel du Python vers le Basic retournent un tuple résultat inversé qui se présente ainsi :
  • (aOutParam, aOutParamIndex, aParams)
Le résultat des fonctions Basic MsgBox et InputBox est récupéré par :
  • return res[0]
Sources
Python2Basic.odt
Appel de routines Basic depuis Python
(74.65 Kio) Téléchargé 15 fois
libO 5.3 32bit sur Win7 x64 & Win10 x64 | libO 5.2 sur Ubuntu 16.10 Yakkety Yak x64 | aOOo 4.2.6.3 sur Mint 17.1 x64
Boîte à Outils Python: StarUML, Geany, PyCharm et bien sûr Apso, MRI, xRay, Object Inspector..
https://wiki.documentfoundation.org/Macros/Design_Guide/fr
LibreOfficiant
NOOuvel adepte
NOOuvel adepte
 
Message(s) : 18
Inscrit le : 03 Jan 2017 15:54


Retour vers Enrichissez la documentation

Qui est en ligne ?

Utilisateur(s) parcourant ce forum : Aucun utilisateur inscrit et 1 invité