2 # -*- coding: utf-8 -*-
4 Extension pour faciliter l'intégration du logiciel Coda.
5 Macro pour faciliter l'intégration des données provenant de Coda dans
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
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
29 COLONNES_NUMERIQUES
= ('Montant doc','Montant EUR')
31 ##############################################################################
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)
41 for flavor
in contents
.getTransferDataFlavors():
42 if flavor
.MimeType
== mimetype
:
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 ?" \
49 data
= contents
.getTransferData(found_flavor
)
50 #print "Data:\n", "-" * 78, "\n", data
53 ##############################################################################
55 def main(ctx
, action
):
56 data
= clipboard_data(ctx
)
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])$')
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'
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()]
75 raise RuntimeError, u
"Action inconnue (erreur de programmation)."
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') == '':
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'):
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
)
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','')
100 if not diff
and value
!= first
:
102 if flag_date
and not re
.match(date_pattern
, value
):
104 if flag_numeric
and value
!= '#DEV!' and not re
.match(numeric_pattern
, value
):
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
]
114 # si la colonne contient des dates, conversion des valeurs comme telles
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
)
121 (day
, month
, year
) = [int(x
) for x
in m
.groups()]
126 value
= '%02d/%02d/%04d' % (month
, day
, year
)
127 data
[row
][column
] = value
128 column_type
[column
] = datetime
.date
130 # si la colonne est numérique, conversion des valeurs comme telles
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
139 #print "Data:\n", "-" * 78, "\n", data
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)."
148 # basculement vers la feuille de calcul concernée, création au besoin
149 sheets
= document
.getSheets()
151 sheet
= sheets
.getByName(sheet_name
)
153 except NoSuchElementException
, e
:
154 sheets
.insertNewByName(sheet_name
, 0)
155 sheet
= sheets
.getByName(sheet_name
)
157 controller
= document
.getCurrentController()
158 controller
.setActiveSheet(sheet
)
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)
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)
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])
188 cell_range
= sheet
.getCellRangeByPosition(
189 0, row
, len(data
[0])-1, row
+ len(data
)-1)
190 cursor
= sheet
.createCursorByRange(cell_range
)
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!':
202 # insertion des en-têtes, après les données pour un autofit correct
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
216 if data
[0][column
] in COLONNES_NUMERIQUES
:
217 sheet_column
.NumberFormat
= number_format
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
226 ##############################################################################
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
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
241 tk
= window
.getToolkit()
242 msgbox
= tk
.createWindow(aDescriptor
)
243 msgbox
.setCaptionText("Collage d'interrogation CODA")
244 msgbox
.setMessageText(unicode(message
))
246 return msgbox
.execute()
248 def coller_cumuls(event
=False):
249 u
"""Coller une interrogation des cumuls CODA."""
250 ctx
= uno
.getComponentContext()
257 def coller_details(event
=False):
258 u
"""Coller une interrogation des détails CODA."""
259 ctx
= uno
.getComponentContext()
266 def coller_details_encore(event
=False):
267 u
"""Coller une interrogation des détails CODA supplémentaire."""
268 ctx
= uno
.getComponentContext()
270 main(ctx
, 'details-encore')
275 g_exportedScripts
= (coller_cumuls
, coller_details
, coller_details_encore
)
277 ##############################################################################
279 from com
.sun
.star
.task
import XJobExecutor
281 class CollerCumulsJob(unohelper
.Base
, XJobExecutor
):
282 def __init__(self
, context
):
283 self
._context
= context
285 def trigger(self
, args
):
287 main(self
._context
, 'cumuls')
289 messageBox(self
._context
, e
)
291 class CollerDetailsJob(unohelper
.Base
, XJobExecutor
):
292 def __init__(self
, context
):
293 self
._context
= context
295 def trigger(self
, args
):
297 main(self
._context
, 'details')
299 messageBox(self
._context
, e
)
301 class CollerDetailsEncoreJob(unohelper
.Base
, XJobExecutor
):
302 def __init__(self
, context
):
303 self
._context
= context
305 def trigger(self
, args
):
307 main(self
._context
, 'details-encore')
309 messageBox(self
._context
, e
)
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",),)