...
[iramuteq] / functions.py
1 #!/bin/env python
2 # -*- coding: utf-8 -*-
3 #Author: Pierre Ratinaud
4 #Copyright (c) 2008-2012 Pierre Ratinaud
5 #Lisense: GNU/GPL
6
7 import wx
8 import re
9 from ConfigParser import ConfigParser
10 from subprocess import Popen, call, PIPE
11 import thread
12 import os
13 import sys
14 import csv
15 import platform
16 import traceback
17 import codecs
18 import locale
19 import datetime
20 from copy import copy
21 from shutil import copyfile
22 #from dialog import BugDialog
23 print 'TEST LOGGING funcion'
24 import logging
25
26 log = logging.getLogger('iramuteq')
27
28
29 indices_simi = [u'cooccurrence' ,'pourcentage de cooccurrence',u'Russel',u'Jaccard', 'Kulczynski1', 'Kulczynski2', 'Mountford', 'Fager', 'simple matching', 'Hamman', 'Faith', 'Tanimoto', 'Dice', 'Phi', 'Stiles', 'Michael', 'Mozley', 'Yule', 'Yule2', 'Ochiai', 'Simpson', 'Braun-Blanquet','Chi-squared', 'Phi-squared', 'Tschuprow', 'Cramer', 'Pearson', 'binomial']
30
31
32 class History :
33     def __init__(self, filein, syscoding = 'utf8') :
34         self.filein = filein
35         self.syscoding = syscoding
36         self.corpora = {}
37         self.openedcorpus = {}
38         self.analyses = {}
39         self.history = {}
40         self.opened = {}
41         self.read()
42
43     def read(self) :
44         self.conf = DoConf(self.filein)
45         self.order = {}
46         self.ordera = {}
47         for i, section in enumerate(self.conf.conf.sections()) :
48             if self.conf.conf.has_option(section, 'corpus_name') :
49                 self.corpora[section] = self.conf.getoptions(section)
50                 self.order[len(self.order)] = section
51             else :
52                 self.analyses[section] = self.conf.getoptions(section)
53                 self.ordera[len(self.ordera)] = section
54         todel = []
55         for corpus in self.corpora :
56             self.history[corpus] = copy(self.corpora[corpus])
57         for analyse in self.analyses :
58             if self.analyses[analyse]['corpus'] in self.corpora :
59                 if 'analyses' in self.history[self.analyses[analyse]['corpus']] :
60                     self.history[self.analyses[analyse]['corpus']]['analyses'].append(self.analyses[analyse])
61                     todel.append(analyse)
62                 else :
63                     self.history[self.analyses[analyse]['corpus']]['analyses'] = [self.analyses[analyse]]
64                     todel.append(analyse)
65             else :
66                  self.history[analyse] = self.analyses[analyse]
67         #for analyse in todel :
68         #    del self.analyses[analyse]
69     
70     def write(self) :
71         sections = self.corpora.keys() + self.analyses.keys()
72         parametres = [self.corpora[key] for key in self.corpora.keys()] + [self.analyses[key] for key in self.analyses.keys()]
73         self.conf.makeoptions(sections, parametres)
74         log.info('write history')
75
76     def add(self, analyse) :
77         if 'corpus' in analyse :
78             if analyse['corpus'] in self.corpora :
79                 if 'analyses' in self.history[analyse['corpus']] :
80                     self.history[analyse['corpus']]['analyses'].append(analyse)
81                 else :
82                     self.history[analyse['corpus']]['analyses'] = [analyse]
83                 self.analyses[analyse['uuid']] = analyse
84             else :
85                 self.analyses[analyse['uuid']] = analyse
86         elif 'corpus_name' in analyse :
87             self.history[analyse['uuid']] = analyse
88             self.corpora[analyse['uuid']] = analyse
89         self.write()
90
91     def delete(self, uuid, corpus = False) :
92         if corpus :
93             del self.corpora[uuid]
94             self.conf.conf.remove_section(uuid)
95             for analyse in self.history[uuid].get('analyses', [False]) :
96                 if analyse :
97                     del self.analyses[analyse['uuid']]
98                     self.conf.conf.remove_section(analyse['uuid'])
99         else :
100             del self.analyses[uuid]
101             self.conf.conf.remove_section(uuid)
102         self.write()
103
104     def addtab(self, analyse) :
105         self.opened[analyse['uuid']] = analyse
106
107     def rmtab(self, analyse) :
108         del self.opened[analyse['uuid']]
109
110 class DoConf :
111     def __init__(self, configfile=None, diff = None, parametres = None) :
112         self.configfile = configfile
113         self.conf = ConfigParser()
114         if configfile is not None :
115             self.conf.readfp(codecs.open(configfile, 'r', 'utf8'))
116         self.parametres = {}
117         if parametres is not None :
118             self.doparametres(parametres)
119
120     def doparametres(self, parametres) :
121         return parametres
122
123     def getsections(self) :
124         return self.conf.sections()
125
126     def getoptions(self, section = None, diff = None):
127         parametres = {}
128         if section is None :
129             section = self.conf.sections()[0]
130         for option in self.conf.options(section) :
131             if self.conf.get(section, option).isdigit() :
132                 parametres[option] = int(self.conf.get(section, option))
133             else :
134                 parametres[option] = self.conf.get(section, option)
135         if 'type' not in parametres :
136             parametres['type'] = section
137         return parametres
138             
139     def makeoptions(self, sections, parametres, outfile = None) :
140         for i, section in enumerate(sections) :
141             if not self.conf.has_section(section) :
142                 self.conf.add_section(section)
143             for option in parametres[i] :
144                 if isinstance(parametres[i][option], int) :
145                     self.conf.set(section, option, `parametres[i][option]`)
146                 elif isinstance(parametres[i][option], basestring) :
147                     self.conf.set(section, option, parametres[i][option].encode('utf8'))
148         if outfile is None :
149             outfile = self.configfile
150         print outfile
151         with codecs.open(outfile, 'w', 'utf8') as f :
152             self.conf.write(f)
153
154     def totext(self, parametres) :
155         txt = ['Corpus']
156         for val in parametres :
157             if isinstance(parametres[val], int) :
158                 txt.append(' \t\t: '.join([val, `parametres[val]`]))
159             else :
160                 txt.append(' \t\t: '.join([val, parametres[val]]))
161         return '\n'.join(txt)
162
163
164 def write_tab(tab, fileout) :
165         writer = csv.writer(open(fileout, 'wb'), delimiter=';', quoting = csv.QUOTE_NONNUMERIC)
166         writer.writerows(tab)
167
168 class BugDialog(wx.Dialog):
169     def __init__(self, *args, **kwds):
170         # begin wxGlade: MyDialog.__init__
171         kwds["style"] = wx.DEFAULT_DIALOG_STYLE
172         kwds["size"] = wx.Size(500, 200)
173         wx.Dialog.__init__(self, *args, **kwds)
174         self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
175         self.button_1 = wx.Button(self, wx.ID_OK, "")
176
177         self.__set_properties()
178         self.__do_layout()
179         # end wxGlade
180
181     def __set_properties(self):
182         # begin wxGlade: MyDialog.__set_properties
183         self.SetTitle("Bug")
184         self.SetMinSize(wx.Size(500, 200))
185         self.text_ctrl_1.SetMinSize(wx.Size(500, 200))
186         
187         # end wxGlade
188
189     def __do_layout(self):
190         # begin wxGlade: MyDialog.__do_layout
191         sizer_1 = wx.BoxSizer(wx.VERTICAL)
192         sizer_1.Add(self.text_ctrl_1, 1, wx.EXPAND, 0)
193         sizer_1.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
194         self.SetSizer(sizer_1)
195         sizer_1.Fit(self)
196         self.Layout()
197
198
199 def CreateIraFile(DictPathOut, clusternb, corpname='corpus_name', section = 'analyse'):
200     AnalyseConf = ConfigParser()
201     AnalyseConf.read(DictPathOut['ira'])
202     AnalyseConf.add_section(section)
203     date = datetime.datetime.now().ctime()
204     AnalyseConf.set(section, 'date', str(date))
205     AnalyseConf.set(section, 'clusternb', clusternb)
206     AnalyseConf.set(section, 'corpus_name', corpname)
207
208     fileout = open(DictPathOut['ira'], 'w')
209     AnalyseConf.write(fileout)
210     fileout.close()
211
212 def sortedby(list, direct, *indices):
213
214     """
215         sortedby: sort a list of lists (e.g. a table) by one or more indices
216                   (columns of the table) and return the sorted list
217
218         e.g.
219          for list = [[2,3],[1,2],[3,1]]:
220          sortedby(list,1) will return [[3, 1], [1, 2], [2, 3]],
221          sortedby(list,0) will return [[1, 2], [2, 3], [3, 1]]
222     """
223
224     nlist = map(lambda x, indices=indices: 
225                  map(lambda i, x=x: x[i], indices) + [x],
226                  list)
227     if direct == 1:
228         nlist.sort()
229     elif direct == 2:
230         nlist.sort(reverse=True)
231     return map(lambda l: l[-1], nlist)
232
233 def add_type(line, dictlem):
234     if line[4] in dictlem:
235         line.append(dictlem[line[4]])
236     else :
237         line.append('')
238     return line
239
240 def treat_line_alceste(i, line) :
241     if line[0] == '*' or line[0] == '*****' :
242         return line + ['']
243     if line[5] == 'NA':
244         print 'NA', line[5]
245         pass
246     elif float(line[5].replace(',', '.')) < 0.0001:
247         line[5] = '< 0,0001'
248     elif float(line[5].replace(',', '.')) > 0.05:
249         line[5] = 'NS (%s)' % str(float(line[5].replace(',', '.')))[0:7]
250     else:
251         line[5] = str(float(line[5].replace(',', '.')))[0:7]
252     return [i, int(line[0]), int(line[1]), float(line[2]), float(line[3]), line[6], line[4], line[5]]
253
254 def ReadProfileAsDico(parent, File, Alceste=False, encoding = sys.getdefaultencoding()):
255     #print 'lecture des profils : ReadProfileAsDico'
256     #if Alceste :
257     #    print 'lecture du dictionnaire de type'
258     #    dictlem = {}
259     #    for line in parent.corpus.lem_type_list :
260     #        dictlem[line[0]] = line[1]
261     dictlem = {}
262     print 'lecture des profiles'
263     #encoding = sys.getdefaultencoding()
264     print encoding
265     FileReader = codecs.open(File, 'r', encoding)
266     Filecontent = FileReader.readlines()
267     FileReader.close()
268     DictProfile = {}
269     count = 0
270     rows = [row.replace('\n', '').replace("'", '').replace('\"', '').replace(',', '.').replace('\r','').split(';') for row in Filecontent]
271     rows.pop(0)
272     ClusterNb = rows[0][2]
273     rows.pop(0)
274     clusters = [row[2] for row in rows if row[0] == u'**']
275     valclusters = [row[1:4] for row in rows if row[0] == u'****']
276     lp = [i for i, line in enumerate(rows) if line[0] == u'****']
277     prof = [rows[lp[i] + 1:lp[i+1] - 1] for i in range(0, len(lp)-1)] + [rows[lp[-1] + 1:len(rows)]] 
278     if Alceste :
279         prof = [[add_type(row, dictlem) for row in pr] for pr in prof]
280         prof = [[treat_line_alceste(i,line) for i, line in enumerate(pr)] for pr in prof] 
281     else :
282         prof = [[line + [''] for line in pr] for pr in prof]
283         prof = [[treat_line_alceste(i,line) for i, line in enumerate(pr)] for pr in prof]
284     for i, cluster in enumerate(clusters):
285         DictProfile[cluster] = [valclusters[i]] + prof[i]
286     return DictProfile
287
288 def GetTxtProfile(dictprofile) :
289     proflist = []
290     for classe in range(0, len(dictprofile)) :
291         prof = dictprofile[str(classe + 1)]
292         clinfo = prof[0]
293         proflist.append('\n'.join([' '.join(['classe %i' % (classe + 1), '-', '%s uce sur %s - %s%%' % (clinfo[0], clinfo[1], clinfo[2])]), '\n'.join(['%5s|%5s|%6s|%6s|%8s|%8s|%20s\t%10s' % tuple([str(val) for val in line]) for line in prof if len(line)==8])]))
294     return '\n\n'.join(proflist)
295
296 def formatExceptionInfo(maxTBlevel=5):
297          cla, exc, trbk = sys.exc_info()
298          excName = cla.__name__
299          try:
300              excArgs = exc.__dict__["args"]
301          except KeyError:
302              excArgs = "<no args>"
303          excTb = traceback.format_tb(trbk, maxTBlevel)
304          return (excName, excArgs, excTb)
305
306
307 #fonction des etudiants de l'iut
308 def decoupercharact(chaine, longueur, longueurOptimale, separateurs = None) :
309     """
310         on part du dernier caractère, et on recule jusqu'au début de la chaîne.
311         Si on trouve un '$', c'est fini.
312         Sinon, on cherche le meilleur candidat. C'est-à-dire le rapport poids/distance le plus important.
313     """
314     separateurs = [[u'.', 60.0], [u'?', 60.0], [u'!', 60.0], [u'£', 60], [u':', 50.0], [u';', 40.0], [u',', 10.0], [u' ', 0.1]]
315     trouve = False                 # si on a trouvé un bon séparateur
316     iDecoupe = 0                # indice du caractere ou il faut decouper
317     
318     # on découpe la chaine pour avoir au maximum 240 caractères
319     longueur = min(longueur, len(chaine) - 1)
320     chaineTravail = chaine[:longueur + 1]
321     nbCar = longueur
322     meilleur = ['', 0, 0]        # type, poids et position du meilleur separateur
323     
324     # on vérifie si on ne trouve pas un '$'
325     indice = chaineTravail.find(u'$')
326     if indice > -1:
327         trouve = True
328         iDecoupe = indice
329
330     # si on ne trouve rien, on cherche le meilleur séparateur
331     if not trouve:
332         while nbCar >= 0:
333             caractere = chaineTravail[nbCar]
334             distance = abs(longueurOptimale - nbCar) + 1
335             meilleureDistance = abs(longueurOptimale - meilleur[2]) + 1
336
337             # on vérifie si le caractére courant est une marque de ponctuation
338             for s in separateurs:
339                 if caractere == s[0]:
340                     # si c'est une ponctuation 
341                     
342                     if s[1] / distance > float(meilleur[1]) / meilleureDistance:
343                         # print nbCar, s[0]
344                         meilleur[0] = s[0]
345                         meilleur[1] = s[1]
346                         meilleur[2] = nbCar
347                         trouve = True
348                         iDecoupe = nbCar
349                         
350                     # et on termine la recherche
351                     break
352
353             # on passe au caractère précédant
354             nbCar = nbCar - 1
355     
356     # si on a trouvé
357     if trouve:
358         fin = chaine[iDecoupe + 1:]
359         retour = chaineTravail[:iDecoupe]
360         return len(retour) > 0, retour.split(), fin
361     # si on a rien trouvé
362     return False, chaine.split(), ''
363
364 def BugReport(parent):
365     for ch in parent.GetChildren():
366         if "<class 'wx._windows.ProgressDialog'>" == str(type(ch)):
367             ch.Destroy()   
368     dial = BugDialog(parent)
369     txt = u'            !== BUG ==!       \n'
370     txt += u'*************************************\n'
371     for line in formatExceptionInfo():
372         if type(line) == type([]):
373             for don in line:
374                 txt += don.replace('    ', ' ')
375         else:
376             txt += line + '\n'
377     if 'Rerror' in dir(parent) :
378         txt += parent.Rerror
379         parent.Rerror = ''
380     print formatExceptionInfo()
381     log.error(txt)
382     dial.text_ctrl_1.write(txt)
383     dial.CenterOnParent()
384     dial.ShowModal()
385     raise NameError('Bug')
386     
387 def PlaySound(parent):
388     if parent.pref.getboolean('iramuteq', 'sound') :
389         try:
390             if "gtk2" in wx.PlatformInfo:
391                 error = Popen(['aplay','-q',os.path.join(parent.AppliPath,'son_fin.wav')])
392             else :    
393                 sound = wx.Sound(os.path.join(parent.AppliPath, 'son_fin.wav'))
394                 sound.Play(wx.SOUND_SYNC)
395         except :
396             print 'pas de son'
397
398 def ReadDicoAsDico(dicopath):
399     with codecs.open(dicopath, 'r', 'UTF8') as f:
400         content = f.readlines()
401     dico = {}
402     for line in content :
403         if line[0] != u'':
404             line = line.replace(u'\n', '').replace('"', '').split('\t')
405             dico[line[0]] = line[1:]
406     return dico
407
408 def ReadLexique(parent, lang = 'french'):
409     parent.lexique = ReadDicoAsDico(parent.DictPath.get(lang, 'french'))
410
411 def ReadList(filein, encoding = sys.getdefaultencoding()):
412     #file = open(filein)
413     file = codecs.open(filein, 'r', encoding)
414     content = file.readlines()
415     file.close()
416     first = content.pop(0)
417     first = first.replace('\n', '').replace('\r','').replace('\"', '').split(';')
418     dict = {}
419     i = 0
420     for line in content:
421         line = line.replace('\n', '').replace('\r','').replace('\"', '').replace(',', '.')
422         line = line.split(';')
423         nline = [line[0]]
424         for val in line[1:]:
425             if val == u'NA' :
426                 don = ''
427             else: 
428                 try:
429                     don = int(val)
430                 except:
431                     don = float('%.5f' % float(val))
432             nline.append(don)
433         dict[i] = nline
434         i += 1
435     return dict, first
436
437 def exec_rcode(rpath, rcode, wait = True, graph = False):
438     print rpath, rcode
439     needX11 = False
440     if sys.platform == 'darwin' :
441         try :
442             macversion = platform.mac_ver()[0].split('.')
443             print macversion
444             if int(macversion[1]) < 5 :
445                 needX11 = True
446             else :
447                 needX11 = False
448         except :
449             needX11 = False
450
451     rpath = rpath.replace('\\','\\\\')
452     if not graph :
453         if wait :
454             if sys.platform == 'win32':
455                 error = call(["%s" % rpath, "--vanilla","--slave","-f", "%s" % rcode])
456             else :
457                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
458             return error
459         else :
460             if sys.platform == 'win32':
461                 pid = Popen(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
462             else :
463                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
464             return pid
465     else :
466         if wait :
467             if sys.platform == 'win32':
468                 error = call(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
469             elif sys.platform == 'darwin' and needX11:
470                 os.environ['DISPLAY'] = ':0.0'
471                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
472             else :
473                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
474             return error
475         else :
476             if sys.platform == 'win32':
477                 pid = Popen(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
478             elif sys.platform == 'darwin' and needX11:
479                 os.environ['DISPLAY'] = ':0.0'
480                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
481             else :
482                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
483             return pid
484
485 def check_Rresult(parent, pid) :
486     if isinstance(pid, Popen) :
487         if pid.returncode != 0 :
488             error = pid.communicate()
489             error = [str(error[0]), error[1]]
490             if error[1] is None :
491                 error[1] = 'None'
492             parent.Rerror = '\n'.join([str(pid.returncode), '\n'.join(error)])
493             try :
494                 raise Exception('\n'.join(u'Erreur R', '\n'.join(error[1:])))
495             except :
496                 BugReport(parent)
497     else :
498         if pid !=0 :
499             try :
500                 raise Exception(u'Erreur R')
501             except :
502                 BugReport(parent)
503
504 def print_liste(filename,liste):
505     with open(filename,'w') as f :
506         for graph in liste :
507             f.write(';'.join(graph)+'\n')
508
509 def read_list_file(filename, encoding = sys.getdefaultencoding()):
510     with codecs.open(filename,'rU', encoding) as f :
511         content=f.readlines()
512         ncontent=[line.replace('\n','').split(';') for line in content if line.strip() != '']
513     return ncontent
514         
515 class MessageImage(wx.Frame):
516     def __init__(self, *args, **kwds):
517         # begin wxGlade: MyFrame.__init__
518         kwds["style"] = wx.DEFAULT_FRAME_STYLE
519         wx.Frame.__init__(self, *args, **kwds)
520         #self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
521         self.imageFile = False
522         self.imagename = u"chi_classe.png"
523         self.HtmlPage = wx.html.HtmlWindow(self, -1)
524         if "gtk2" in wx.PlatformInfo:
525             self.HtmlPage.SetStandardFonts()
526         self.HtmlPage.SetFonts('Courier', 'Courier')
527         
528         self.button_1 = wx.Button(self, -1, u"Fermer")
529         self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.button_1)
530         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
531         self.__do_layout()
532         # end wxGlade
533
534     def __do_layout(self):
535         # begin wxGlade: MyFrame.__do_layout
536         sizer_1 = wx.BoxSizer(wx.VERTICAL)
537         self.sizer_2 = wx.BoxSizer(wx.VERTICAL)
538         self.sizer_2.Add(self.HtmlPage, 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
539         self.sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0)
540         sizer_1.Add(self.sizer_2, 1, wx.EXPAND, 0)
541         self.SetAutoLayout(True)
542         self.SetSizer(sizer_1)
543         # end wxGlade
544
545     def addsaveimage(self, imageFile) :
546         self.imageFile = imageFile
547         self.button_2 = wx.Button(self, -1, u"Enregistrer l'image...")
548         self.Bind(wx.EVT_BUTTON, self.OnSaveImage, self.button_2)
549         self.sizer_2.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0)
550         self.Layout()
551
552     def OnCloseMe(self, event):
553         self.Close(True)
554
555     def OnCloseWindow(self, event):
556         self.Destroy()
557
558     def OnSaveImage(self, event) :
559         dlg = wx.FileDialog(
560             self, message="Enregistrer sous...", defaultDir=os.getcwd(),
561             defaultFile= self.imagename, wildcard="png|*.png", style=wx.SAVE | wx.OVERWRITE_PROMPT
562             )
563         dlg.SetFilterIndex(2)
564         dlg.CenterOnParent()
565         if dlg.ShowModal() == wx.ID_OK:
566             path = dlg.GetPath()
567             copyfile(self.imageFile, path)
568             
569
570 def progressbar(self, maxi) :
571     if 'parent' in dir(self) :
572         parent = self.parent
573     else :
574         parent = self
575     return wx.ProgressDialog("Traitements",
576                              "Veuillez patienter...",
577                              maximum=maxi,
578                              parent=parent,
579                              style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT
580                              )
581
582
583 def treat_var_mod(variables) :
584     var_mod = {}
585     for variable in variables :
586         if u'_' in variable :
587             forme = variable.split(u'_')
588             var = forme[0]
589             mod = forme[1]
590             if not var in var_mod :
591                 var_mod[var] = [variable]
592             else :
593                 if not mod in var_mod[var] :
594                     var_mod[var].append(variable)
595     return var_mod