patch from David Skalinder
[iramuteq] / tableau.py
1 # -*- coding: utf-8 -*-
2 #Author: Pierre Ratinaud
3 #Copyright (c) 2010 Pierre Ratinaud
4 #License: GNU/GPL
5
6 import codecs
7 import sys
8 import xlrd
9 import ooolib
10 import os
11 import tempfile
12 import re
13 import htmlentitydefs
14 import shelve
15 from functions import DoConf
16 from uuid import uuid4
17 from chemins import PathOut
18 import logging
19
20 log = logging.getLogger('iramuteq.tableau')
21
22 ##
23 # Removes HTML or XML character references and entities from a text string.
24 #
25 # @param text The HTML (or XML) source text.
26 # @return The plain text, as a Unicode string, if necessary.
27
28 def unescape(text):
29     def fixup(m):
30         #apos is not in the dictionnary
31         htmlentitydefs.name2codepoint['apos'] = ord("'")
32         text = m.group(0)
33         if text[:2] == "&#":
34             # character reference
35             try:
36                 if text[:3] == "&#x":
37                     return unichr(int(text[3:-1], 16))
38                 else:
39                     return unichr(int(text[2:-1]))
40             except ValueError:
41                 pass
42         else:
43             try:
44                 text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
45             except KeyError:
46                 pass
47         return text # leave as is
48     return re.sub("&#?\w+;", fixup, text)
49
50 def UpdateDico(Dico, word, line):
51     if word in Dico :
52         Dico[word][0] += 1
53         Dico[word][1].append(line)
54     else:
55         Dico[word] = [1, [line]]
56         
57 def copymatrix(tableau):
58     log.info('copy matrix')
59     copymat = Tableau(tableau.parent, parametres = tableau.parametres)
60     copymat.linecontent = tableau.linecontent
61     copymat.csvtable = tableau.csvtable
62     copymat.pathout = tableau.pathout
63     copymat.colnames = tableau.colnames
64     copymat.rownb = tableau.rownb
65     copymat.colnb = tableau.colnb
66     if copymat.csvtable is None :
67         copymat.open()
68     return copymat
69
70 class Tableau() :
71     def __init__(self, parent, filename = '', filetype = 'csv', encodage = 'utf-8', parametres = None) :
72         self.parent = parent
73         if parametres is None :
74             self.parametres = DoConf(self.parent.ConfigPath['matrix']).getoptions('matrix')
75             self.parametres['pathout'] = PathOut(filename, 'matrix').mkdirout()
76             self.parametres['originalpath'] = filename
77             self.parametres['filetype'] = filetype
78             self.parametres['encodage'] = encodage
79             #self.parametre['pathout'] = os.path.dirname(os.path.abspath(filename))
80             self.parametres['mineff'] = 3
81             self.parametres['syscoding'] = sys.getdefaultencoding()
82             self.parametres['type'] = 'matrix'
83             self.parametres['matrix_name'] = os.path.basename(filename)
84             self.parametres['uuid'] = str(uuid4())
85             self.parametres['shelves'] = os.path.join(self.parametres['pathout'], 'shelve.db')
86             self.parametres['ira'] = os.path.join(self.parametres['pathout'], 'Matrix.ira')
87         else :
88             self.parametres = parametres
89         self.pathout = PathOut(filename = filename, dirout = self.parametres['pathout'])
90         self.csvtable = None
91         self.sups = {}
92         self.actives = {}
93         self.listactives = None
94         self.content = []
95         self.linecontent = []
96         self.isbinary = False
97         self.binary = []
98         self.firstrowiscolnames = True
99         self.colnames = []
100         self.firstcolisrownames = True
101         self.rownames = []
102         self.colnb = 0
103         self.rownb = 0
104         self.classes = []
105         #self.parametres = self.parametre
106
107     def read_tableau(self, fileout) :
108         d=shelve.open(fileout)
109         #self.parametres = d['parametres']
110         #if 'syscoding' not in self.parametres :
111         #    self.parametres['syscoding'] = sys.getdefaultencoding()
112         self.actives = d['actives']
113         self.sups = d['sups']
114         self.classes = d['classes']
115         self.listactives = d['listactives']
116         if 'listet' in d :
117             self.listet = d['listet']
118         if 'selected_col' in d :
119             self.selected_col = d['selected_col']
120         if 'datas' in d :
121             self.datas = d['datas']
122         if 'lchi' in d :
123             self.lchi = d['lchi']
124         if 'content' in d :
125             self.content = d['content']
126         d.close()
127     
128     def open(self):
129         print 'open matrix'
130         self.read_csvfile()
131         self.colnames = self.csvtable[0][1:]
132         self.rownb = len(self.linecontent)
133         self.colnb = len(self.linecontent[0])
134
135     def save_tableau(self, fileout) :
136         d=shelve.open(fileout)
137         d['parametres'] = self.parametres
138         d['actives'] = self.actives
139         d['sups'] = self.sups
140         d['classes'] = self.classes
141         d['listactives'] = self.listactives
142         if 'listet' in dir(self) :
143             d['listet'] = self.listet
144         if 'selected_col' in dir(self) :
145             d['selected_col'] = self.selected_col
146         if 'datas' in dir(self) :
147             d['datas'] = self.datas
148         if 'lchi' in dir(self) :
149             d['lchi'] = self.lchi
150         d['content'] = self.content
151         d.close()
152
153     def make_content(self) :
154         self.pathout.createdir(self.parametres['pathout'])
155         if self.parametres['filetype'] == 'csv' :
156             self.read_csv()
157         elif self.parametres['filetype'] == 'xls' :
158             self.read_xls()
159         elif self.parametres['filetype'] == 'ods' :
160             self.read_ods()
161         self.parametres['csvfile'] = os.path.join(self.parametres['pathout'], 'csvfile.csv')
162         self.make_tmpfile()
163         DoConf().makeoptions(['matrix'],[self.parametres], self.parametres['ira'])
164         self.parent.history.addMatrix(self.parametres)
165
166     def read_xls(self) :
167         #FIXME : encodage
168         #print '############## ENCODING IN EXCEL #######################'
169         #datafile = xlrd.open_workbook(self.parametre['filename'], encoding_override="azerazerazer")
170         datafile = xlrd.open_workbook(self.parametres['originalpath'])
171         datatable = datafile.sheet_by_index(self.parametres['sheetnb']-1)
172         self.linecontent = [[str(datatable.cell_value(rowx = i, colx = j)).replace(u'"','').replace(u';',' ').replace(u'\n',' ').replace('\r', ' ').replace('\t', ' ').strip() for j in range(datatable.ncols)] for i in range(datatable.nrows)]
173
174     def read_ods(self) :
175         doc = ooolib.Calc(opendoc=self.parametres['originalpath'])
176         doc.set_sheet_index(0)
177         (cols, rows) = doc.get_sheet_dimensions()
178         for row in range(1, rows + 1):
179             ligne = []
180             for col in range(1, cols + 1):
181                 data = doc.get_cell_value(col, row)
182                 if data is not None :
183                     ligne.append(unescape(data[1].replace(u'"','').replace(u';',' ').replace(u'\n', ' ').replace('\t', ' ').strip()))
184                 else :
185                     ligne.append('')
186             self.linecontent.append(ligne)
187
188     def read_csv(self) :
189         with codecs.open(self.parametres['originalpath'], 'r', self.parametres['encodage']) as f :
190             content = f.read() 
191         self.linecontent = [line.split(self.parametres['colsep']) for line in content.splitlines()]
192         self.linecontent = [[val.replace(u'"','').replace(u';',' ').replace('\t', ' ').strip() for val in line] for line in self.linecontent]
193
194     def write_csvfile(self) :
195         with open(self.parametres['csvfile'], 'w') as f :
196             f.write('\n'.join(['\t'.join(line) for line in self.csvtable]))
197
198     def make_tmpfile(self) :
199         self.rownb = len(self.linecontent)
200         self.colnb = len(self.linecontent[0])
201         if self.firstrowiscolnames :
202             self.colnames = self.linecontent[0]
203             self.linecontent.pop(0)
204             self.rownb -= 1
205         else :
206             self.colnames = ['_'.join([u'colonne', `i`]) for i in range(self.colnb)]
207         if self.firstcolisrownames :
208             self.rownames = [row[0] for row in self.linecontent]
209             self.linecontent = [row[1:] for row in self.linecontent]
210             self.colnb -= 1
211             self.idname = self.colnames[0]
212             self.colnames.pop(0)
213             self.check_rownames()
214         else :
215             self.rownames = [`i` for i in range(self.rownb)]
216             self.idname = u'identifiant'
217         self.csvtable = [[self.idname] + self.colnames] + [[self.rownames[i]] + self.linecontent[i] for i in range(len(self.rownames))] 
218         self.write_csvfile()
219
220     def read_csvfile(self):
221         with codecs.open(self.parametres['csvfile'], 'r', self.parametres['syscoding']) as f:
222             self.csvtable = [line.split('\t') for line in f.read().splitlines()]
223         self.linecontent = [line[1:] for line in self.csvtable]
224         self.linecontent.pop(0)
225         
226     def extractfrommod(self, col, val):
227         return ([''] + self.colnames) + [line for line in self.csvtable[1:] if line[col + 1] == val]
228
229     def splitfromvar(self, col, var):
230         newtabs = {}
231         for line in self.csvtable[1:] :
232             mod = line[col]
233             if mod in newtabs :
234                 newtabs[mod].append(line)
235             else :
236                 newtabs[mod] = [line]
237         return ([''] + self.colnames) + newtab
238
239
240     def check_rownames(self) :
241         if len(self.rownames) == len(list(set(self.rownames))) :
242             print u'row names ok'
243         else :
244             print u'les noms de lignes ne sont pas uniques, ils sont remplaces'
245             self.rownames = [`i` for i in range(self.rownb)]
246
247     def make_unique_list(self) :
248         return list(set([val for line in self.linecontent for val in line if val.strip() != '']))
249
250     def make_dico(self, selcol) :
251         dico = {}
252         for i, line in enumerate(selcol) :
253             for forme in line:
254                 if forme.strip() != '' :
255                     UpdateDico(dico, forme, i)
256         return dico
257     
258     def select_col(self, listcol) :
259         dc = dict(zip(listcol, listcol))
260         selcol = [[val for i, val in enumerate(row) if i in dc] for row in self.linecontent]
261         return selcol
262     
263     def countmultiple(self, liscol):
264         return self.make_dico(self.select_col(liscol))
265
266     def getactlistfromselection(self, listact) :
267         selcol = self.select_col(listact)
268         self.actives = self.make_dico(selcol)
269         return [[val, self.actives[val][0]] for val in self.actives]       
270
271     def make_listactives(self) :
272         self.listactives = [val for val in self.actives if val != 'NA' and self.actives[val] >= self.parametres['mineff']]
273     
274     def write01(self, fileout, dico, linecontent) :
275         if self.listactives is None :
276             self.listactives = [val for val in dico if val != 'NA' and dico[val] >= self.parametres['mineff']]
277         out = [['0' for forme in self.listactives] for line in linecontent]
278         for i, forme in enumerate(self.listactives) :
279             for line in dico[forme][1] :
280                 out[line][i] = '1'
281         #out = [[self.rownames[i]] + out[i] for i in range(len(linecontent))] 
282         #out.insert(0,[self.idname] + self.listactives)
283         out.insert(0, self.listactives)
284         with open(fileout, 'w') as f :
285             f.write('\n'.join([';'.join(line) for line in out]))
286
287     def make_01_from_selection(self, listact, listsup = None, dowrite = True) :
288         selcol = self.select_col(listact)
289         self.actives = self.make_dico(selcol)
290         self.write01(self.pathout['mat01.csv'], self.actives, selcol)
291         if listsup is not None :
292             selcol = self.select_col(listsup)
293             self.sups = self.make_dico(selcol)
294
295     def make_01_alc_format(self, fileout) :
296         for i, ligne in enumerate(self.linecontent) :
297             for forme in ligne:
298                 if len(forme) >= 1:
299                     if forme[0] == u'*':
300                         UpdateDico(self.sups, forme, i)
301                     else:
302                         UpdateDico(self.actives, forme, i)        
303         self.listactives = [val for val in self.actives if self.actives[val][0] >= self.parametres['mineff']]
304         table = [['0' for i in range(len(self.listactives))] for j in range(self.rownb)]
305         for i, val in enumerate(self.listactives) :
306             for j, line in enumerate(self.linecontent) :
307                 if val in line :
308                     table[j][i] = '1'
309         #table = [[self.rownames[i]] + table[i] for i in range(len(self.rownames))]
310         #table.insert(0, [self.idname] + self.listactives)
311         table.insert(0, self.listactives)
312         with open(fileout, 'w') as f:
313             f.write('\n'.join([';'.join(line) for line in table]))
314
315     def printtable(self, filename, Table, sep = ';'):
316         with open(filename, 'w') as f :
317             f.write('\n'.join([sep.join(line) for line in Table]))
318     
319     def buildprofil(self) :
320         with open(self.pathout['uce'], 'rU') as filein :
321             content = filein.readlines()
322         content.pop(0)
323         lsucecl = []
324         dicocl = {}
325         for i, line in enumerate(content) :
326             line = line.replace('\n', '').replace('"', '').split(';')
327             UpdateDico(dicocl, line[1], i)
328             lsucecl.append([int(line[0]) - 1, int(line[1])])
329         self.classes = lsucecl
330         nlist = [[nbuce, cl] for nbuce, cl in lsucecl if cl != 0]
331         self.ucecla = len(nlist)
332         if '0' in dicocl :
333             self.clnb = len(dicocl) - 1
334         else:
335             self.clnb = len(dicocl)
336
337         tablecont = []
338         for active in self.listactives :
339             line = [active]
340             line0 = [0] * self.clnb
341             line += line0
342             for i in range(0, self.clnb) :
343                 for uce, cl in nlist:
344                     if cl == i + 1 :
345                         if active in self.linecontent[uce]:
346                             line[i + 1] += 1
347             if sum(line[1:]) > self.parametres['mineff']:
348                 tablecont.append([line[0]] + [`don` for don in line if type(don) == type(1)])
349         
350         tablecontet = []
351         for sup in self.sups :
352             line = [sup]
353             line0 = [0] * self.clnb
354             line += line0
355             for i in range(0, self.clnb) :
356                 for uce, cl in nlist:
357                     if cl == i + 1 :
358                         if sup in self.linecontent[uce]:
359                             line[i + 1] += 1
360             tablecontet.append([line[0]] + [`don` for don in line if type(don) == type(1)])
361             
362         self.printtable(self.pathout['ContEtOut'], tablecontet)
363         self.printtable(self.pathout['Contout'], tablecont)        
364
365     def get_colnames(self) :
366         return self.colnames[:]
367
368     def make_table_from_classe(self, cl, la) :
369         ln = [line[0] for line in self.classes if line[1] == cl]
370         out = [['0' for col in la] for line in ln]
371         for i, act in enumerate(la) :
372             for j, line in enumerate(ln) :
373                 if line in self.actives[act][1] :
374                     out[j][i] = '1'
375         out.insert(0,[act for act in la])
376         return out
377         
378         
379
380 #filename = 'corpus/cent3.csv'
381 #filename = 'corpus/agir2sortie.csv'
382 #tab = Tableau('',filename, encodage='utf-8')
383 #tab.parametre['csvfile'] = tab.parametre['filename']
384 #tab.parametre['sep'] = '\t'
385 #tab.firstrowiscolnames = True
386 #tab.firstcolisrownames = False
387 #tab.read_data()
388 #tab.make_01('corpus/matrice01.csv')