OpenOffice.org: utilisation des boîtes de dialogue du système par défaut.
[macros-openoffice.git] / auf-poste-client-reflets / openoffice / CODA.py
1 #!
2 # -*- coding: utf-8 -*-
3 """
4 Extension pour faciliter l'intégration du logiciel Coda.
5 Macro pour faciliter l'intégration des données provenant de Coda dans
6 une feuille de calcul.
7
8 Copyright : Agence universitaire de la Francophonie
9 Licence : GNU General Public Licence, version 2
10 Auteur : Jean Christophe André
11 Date de création : septembre 2009
12 """
13 import re
14 import datetime
15 import uno
16 import unohelper
17 from com.sun.star.awt import WindowDescriptor
18 from com.sun.star.awt.WindowClass import MODALTOP
19 from com.sun.star.awt.VclWindowPeerAttribute import OK, DEF_OK
20 from com.sun.star.uno import Exception as UnoException, RuntimeException
21 from com.sun.star.connection import NoConnectException
22 from com.sun.star.lang import Locale, IllegalArgumentException
23 from com.sun.star.beans import PropertyValue
24 #from com.sun.star.util.ParagraphProperties import CENTER
25 from com.sun.star.util.NumberFormat import CURRENCY, DATE, NUMBER
26 from com.sun.star.i18n.NumberFormatIndex import NUMBER_1000DEC2
27 from com.sun.star.container import NoSuchElementException
28
29 COLONNES_NUMERIQUES = ('Montant doc','Montant EUR')
30
31 ##############################################################################
32
33 def clipboard_data(ctx, mimetype='text/plain;charset=utf-16'):
34 clipboard = ctx.ServiceManager.createInstanceWithContext(
35 "com.sun.star.datatransfer.clipboard.SystemClipboard", ctx)
36 contents = clipboard.getContents()
37 #print "Contents:\n%s\n%s\n%s" % ("-" * 78, "* " + "\n* ".join(dir(contents)), "=" * 78)
38 #flavors = contents.getTransferDataFlavors()
39 #print "Flavors:\n%s\n%s\n%s" % ("-" * 78, "* " + "\n* ".join([flavor.MimeType for flavor in flavors]), "=" * 78)
40 found_flavor = None
41 for flavor in contents.getTransferDataFlavors():
42 if flavor.MimeType == mimetype:
43 found_flavor = flavor
44 break
45 if not found_flavor:
46 raise RuntimeError, u"Erreur : type de données '%s' non disponible.\n" \
47 u"\nAvez-vous bien sélectionné puis copié les données dans CODA ?" \
48 % mimetype
49 data = contents.getTransferData(found_flavor)
50 #print "Data:\n", "-" * 78, "\n", data
51 return data
52
53 ##############################################################################
54
55 def main(ctx, action):
56 data = clipboard_data(ctx)
57
58 numeric_pattern = re.compile('^[+-]?[0-9]+([\. ][0-9][0-9][0-9])*([\.,][0-9]+)?$')
59 date_pattern = re.compile('^([0-9]?[0-9])/([0-9]?[0-9])/([0-9]?[0-9]?[0-9]?[0-9])$')
60
61 if action == 'cumuls':
62 sheet_name = u'CODA-Cumuls'
63 # on découpe le texte reçu en lignes puis en colonnes
64 # on ne garde ici que les 10 premières colonnes
65 data = [row.split('\t')[:10] for row in data.splitlines()]
66 data[0][6] = '' # suppression de la 2nde colonne 'Engagement'
67 data[0][8] = '' # suppression de la colonne 'Colonne9'
68
69 elif action == 'details' or action == 'details-encore':
70 sheet_name = u'CODA-Détails'
71 # on découpe le texte reçu en lignes puis en colonnes
72 data = [row.split('\t') for row in data.splitlines()]
73
74 else:
75 raise RuntimeError, u"Action inconnue (erreur de programmation)."
76
77 # on efface la dernière ligne pleine de car. nuls, le cas échéant
78 last = data[len(data)-1]
79 if len(last) == 1 and last[0].strip('\x00') == '':
80 del data[len(data)-1]
81
82 # on détermine le type des données, par colonne :
83 # - on vérifie si toutes les valeurs de la colonne sont identiques
84 # - on vérifie si la colonne ne contient que des dates
85 # - on vérifie si la colonne ne contient que des nombres
86 column_type = [str] * len(data[0])
87 for column in range(len(data[0])-1,-1,-1):
88 # quelques colonnes ne doivent pas être testées
89 if action in ('details','details-encore') and data[0][column] in ('Num doc','PCG'):
90 continue
91 # on prend la première valeur, nettoyée un peu pour l'analyse
92 first = data[1][column].strip().replace(u'\xa0','')
93 flag_date = first and re.match(date_pattern, first)
94 flag_numeric = first and first != '#DEV!' and re.match(numeric_pattern, first)
95 diff = False
96 # parcours des autres valeurs de la colonne
97 for row in range(2,len(data)):
98 value = data[row][column].strip().replace(u'\xa0','')
99 if value:
100 if not diff and value != first:
101 diff = True
102 if flag_date and not re.match(date_pattern, value):
103 flag_date = False
104 if flag_numeric and value != '#DEV!' and not re.match(numeric_pattern, value):
105 flag_numeric = False
106 # s'il y a au moins 2 lignes et que les valeurs dans la colonne
107 # sont toutes identiques et qu'il n'y a pas d'intitulé sur la
108 # colonne alors on supprime toute la colonne
109 if len(data) > 2 and not diff and not data[0][column]:
110 for row in range(len(data)):
111 del data[row][column]
112 del column_type[column]
113 continue
114 # si la colonne contient des dates, conversion des valeurs comme telles
115 if flag_date:
116 for row in range(1,len(data)):
117 value = data[row][column].strip().replace(u'\xa0','')
118 if value and value != '#DEV!':
119 m = re.match(date_pattern, value)
120 if m:
121 (day, month, year) = [int(x) for x in m.groups()]
122 if year < 50:
123 year += 2000
124 elif year < 100:
125 year += 1900
126 value = '%02d/%02d/%04d' % (month, day, year)
127 data[row][column] = value
128 column_type[column] = datetime.date
129 continue
130 # si la colonne est numérique, conversion des valeurs comme telles
131 if flag_numeric:
132 for row in range(1,len(data)):
133 value = data[row][column].strip().replace(u'\xa0','')
134 if value and value != '#DEV!':
135 data[row][column] = float(value.replace('.','').replace(',','.'))
136 column_type[column] = float
137 continue
138
139 #print "Data:\n", "-" * 78, "\n", data
140
141 # récupération du document en cours, vérification du type Spreadsheet
142 desktop = ctx.ServiceManager.createInstanceWithContext(
143 "com.sun.star.frame.Desktop", ctx)
144 document = desktop.getCurrentComponent()
145 if not document.supportsService("com.sun.star.sheet.SpreadsheetDocument"):
146 raise RuntimeError, u"Ce n'est pas un document de type Spreadsheet (Calc)."
147
148 # basculement vers la feuille de calcul concernée, création au besoin
149 sheets = document.getSheets()
150 try:
151 sheet = sheets.getByName(sheet_name)
152 new_sheet = False
153 except NoSuchElementException, e:
154 sheets.insertNewByName(sheet_name, 0)
155 sheet = sheets.getByName(sheet_name)
156 new_sheet = True
157 controller = document.getCurrentController()
158 controller.setActiveSheet(sheet)
159
160 # récupération des formats date, monétaire et nombre en français
161 # http://api.openoffice.org/docs/common/ref/com/sun/star/util/XNumberFormatTypes.html
162 # http://api.openoffice.org/docs/common/ref/com/sun/star/i18n/NumberFormatIndex.html
163 locale_fr_FR = Locale('fr', 'FR', '')
164 number_formats = document.getNumberFormats()
165 date_format = number_formats.getStandardFormat(DATE, locale_fr_FR)
166 currency_format = number_formats.getStandardFormat(CURRENCY, locale_fr_FR)
167 number_format = number_formats.getFormatIndex(NUMBER_1000DEC2, locale_fr_FR)
168 #format_string = number_formats.generateFormat(
169 # NUMBER, locale_fr_FR, True, False, 2, 1)
170 #format_key = number_formats.queryKey(format_string, locale_fr_FR, True)
171 #if format_key < 0:
172 # format_key = number_formats.addNew(format_string, locale_fr_FR)
173 #print "NUMBER_1000DEC2=%s key=%s" % (NUMBER_1000DEC2, format_key)
174 #number_format = number_formats.getByKey(format_key)
175
176 # détermination de la zone de travail
177 if action.endswith('-encore'):
178 # localisation de la la zone déjà utilisée
179 cursor = sheet.createCursor()
180 cursor.gotoEndOfUsedArea(False)
181 cursor.gotoStartOfUsedArea(True)
182 rangeAddress = cursor.getRangeAddress()
183 row = rangeAddress.EndRow
184 # FIXME: on devrait comparer ici rangeAddress.EndColumn avec len(data[0])
185 else:
186 row = 0
187
188 cell_range = sheet.getCellRangeByPosition(
189 0, row, len(data[0])-1, row + len(data)-1)
190 cursor = sheet.createCursorByRange(cell_range)
191
192 # insertion des données
193 for row in range(1, len(data)):
194 for column in range(len(data[row])):
195 value = data[row][column]
196 cell = cursor.getCellByPosition(column, row)
197 if column_type[column] == float and value != '#DEV!':
198 cell.Value = value
199 else:
200 cell.Formula = value
201
202 # insertion des en-têtes, après les données pour un autofit correct
203 if new_sheet:
204 for column in range(len(data[0])):
205 # écriture de l'en-tête de la colonne
206 cell = cursor.getCellByPosition(column, 0)
207 cell.Formula = data[0][column]
208 # formatage par défaut pour la colonne
209 sheet_column = sheet.Columns.getByIndex(column)
210 if column_type[column] == datetime.date:
211 sheet_column.NumberFormat = date_format
212 elif column_type[column] == float:
213 if action == 'cumuls':
214 sheet_column.NumberFormat = currency_format
215 else:
216 if data[0][column] in COLONNES_NUMERIQUES:
217 sheet_column.NumberFormat = number_format
218 else:
219 sheet_column.NumberFormat = currency_format
220 # ajustement automatique, une fois toutes les valeurs écrites
221 sheet_column.OptimalWidth = True
222 # formatage spécifique pour la ligne d'en-têtes
223 headers_row = sheet.Rows.getByIndex(0)
224 headers_row.ParaAdjust = 3 # CENTER
225
226 ##############################################################################
227
228 # Show a message box with the UNO based toolkit
229 def messageBox(ctx, message):
230 document = XSCRIPTCONTEXT.getDocument()
231 window = document.CurrentController.Frame.ContainerWindow
232
233 aDescriptor = WindowDescriptor()
234 aDescriptor.Type = MODALTOP
235 aDescriptor.WindowServiceName = "infobox"
236 aDescriptor.ParentIndex = -1
237 aDescriptor.Parent = window
238 #aDescriptor.Bounds = Rectangle()
239 aDescriptor.WindowAttributes = OK
240
241 tk = window.getToolkit()
242 msgbox = tk.createWindow(aDescriptor)
243 msgbox.setCaptionText("Collage d'interrogation CODA")
244 msgbox.setMessageText(unicode(message))
245
246 return msgbox.execute()
247
248 def coller_cumuls(event=False):
249 u"""Coller une interrogation des cumuls CODA."""
250 ctx = uno.getComponentContext()
251 try:
252 main(ctx, 'cumuls')
253 except Exception, e:
254 messageBox(ctx, e)
255 return None
256
257 def coller_details(event=False):
258 u"""Coller une interrogation des détails CODA."""
259 ctx = uno.getComponentContext()
260 try:
261 main(ctx, 'details')
262 except Exception, e:
263 messageBox(ctx, e)
264 return None
265
266 def coller_details_encore(event=False):
267 u"""Coller une interrogation des détails CODA supplémentaire."""
268 ctx = uno.getComponentContext()
269 try:
270 main(ctx, 'details-encore')
271 except Exception, e:
272 messageBox(ctx, e)
273 return None
274
275 g_exportedScripts = (coller_cumuls, coller_details, coller_details_encore)
276
277 ##############################################################################
278
279 from com.sun.star.task import XJobExecutor
280
281 class CollerCumulsJob(unohelper.Base, XJobExecutor):
282 def __init__(self, context):
283 self._context = context
284
285 def trigger(self, args):
286 try:
287 main(self._context, 'cumuls')
288 except Exception, e:
289 messageBox(self._context, e)
290
291 class CollerDetailsJob(unohelper.Base, XJobExecutor):
292 def __init__(self, context):
293 self._context = context
294
295 def trigger(self, args):
296 try:
297 main(self._context, 'details')
298 except Exception, e:
299 messageBox(self._context, e)
300
301 class CollerDetailsEncoreJob(unohelper.Base, XJobExecutor):
302 def __init__(self, context):
303 self._context = context
304
305 def trigger(self, args):
306 try:
307 main(self._context, 'details-encore')
308 except Exception, e:
309 messageBox(self._context, e)
310
311 g_ImplementationHelper = unohelper.ImplementationHelper()
312 g_ImplementationHelper.addImplementation( \
313 CollerCumulsJob, "org.auf.openoffice.CODA.CollerCumuls", \
314 ("com.sun.star.task.Job",),)
315 g_ImplementationHelper.addImplementation( \
316 CollerDetailsJob, "org.auf.openoffice.CODA.CollerDetails", \
317 ("com.sun.star.task.Job",),)
318 g_ImplementationHelper.addImplementation( \
319 CollerDetailsEncoreJob, "org.auf.openoffice.CODA.CollerDetailsEncore", \
320 ("com.sun.star.task.Job",),)