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