]> SALOME platform Git repositories - tools/eficas.git/blob - Aster/Cata/Utilitai/Table.py
Salome HOME
CCAR: merge de la branche de développement V1_11a1 dans la branche
[tools/eficas.git] / Aster / Cata / Utilitai / Table.py
1 #@ MODIF Table Utilitai  DATE 06/11/2006   AUTEUR MCOURTOI M.COURTOIS 
2 # -*- coding: iso-8859-1 -*-
3 #            CONFIGURATION MANAGEMENT OF EDF VERSION
4 # ======================================================================
5 # COPYRIGHT (C) 1991 - 2004  EDF R&D                  WWW.CODE-ASTER.ORG
6 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY  
7 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY  
8 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR     
9 # (AT YOUR OPTION) ANY LATER VERSION.                                                  
10 #                                                                       
11 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT   
12 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF            
13 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU      
14 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.                              
15 #                                                                       
16 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE     
17 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,         
18 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.        
19 # ======================================================================
20
21 # RESPONSABLE MCOURTOI M.COURTOIS
22 __all__ = ['Table', 'merge']
23
24 import sys
25 import re
26 from copy  import copy
27
28 from types import ListType, TupleType, IntType, LongType, FloatType, ComplexType, \
29                   DictType, StringType, StringTypes, UnicodeType, NoneType
30 EnumTypes = (ListType, TupleType)
31 NumberTypes = (IntType, LongType, FloatType, ComplexType)
32
33 import transpose
34
35 # try/except pour utiliser hors aster
36 try:
37    from Utilitai.Utmess import UTMESS
38 except ImportError:
39    def UTMESS(code,sprg,texte):
40       fmt = '\n <%s> <%s> %s\n\n'
41       if code == 'F':
42          raise StandardError, fmt % (code,sprg,texte)
43       else:
44          print fmt % (code,sprg,texte)
45
46 if not sys.modules.has_key('Graph'):
47    try:
48       from Utilitai import Graph
49    except ImportError:
50       import Graph
51
52 # formats de base (identiques à ceux du module Graph)
53 DicForm = {
54    'csep'  : ' ',       # séparateur
55    'ccom'  : '#',       # commentaire
56    'cdeb'  : '',        # début de ligne
57    'cfin'  : '\n',      # fin de ligne
58    'sepch' : ';',       # séparateur entre deux lignes d'une cellule
59    'formK' : '%-8s',    # chaines
60    'formR' : '%12.5E',  # réels
61    'formI' : '%8d'      # entiers
62 }
63 # type par défaut des chaines de caractères
64 Kdef = 'K24'
65
66 # ------------------------------------------------------------------------------
67 # ------------------------------------------------------------------------------
68 # ------------------------------------------------------------------------------
69 class TableBase(object):
70    """Classe pour partager les méthodes d'impression entre Table et Colonne
71    (c'est surtout utile pour vérifier que l'extraction et les filtres sur les
72    colonnes sont corrects).
73    """
74    def __init__(self):
75       """Constructeur.
76       """
77       self.rows=None
78       self.para=None
79       self.type=None
80       self.titr=None
81    
82    def __repr__(self):
83       return self.ReprTable()
84    def Croise(self, **kargs):
85       raise NotImplementedError, 'Must be defined in a derived class'
86
87    def __len__(self):
88       """Retourne le nombre de ligne dans la Table/Colonne.
89       """
90       return len(self.rows)
91
92 # ------------------------------------------------------------------------------
93    def Impr(self, FICHIER=None, FORMAT='TABLEAU', dform=None, **opts):
94       """Impresssion de la Table selon le format spécifié.
95          FICHIER : nom du(des) fichier(s). Si None, on dirige vers stdout
96          dform : dictionnaire de formats d'impression (format des réels,
97             commentaires, saut de ligne...)
98          opts  : selon FORMAT.
99       """
100       para={
101          'TABLEAU'         : { 'mode' : 'a', 'driver' : self.ImprTableau,   },
102          'ASTER'           : { 'mode' : 'a', 'driver' : self.ImprTableau,   },
103          'XMGRACE'         : { 'mode' : 'a', 'driver' : self.ImprGraph,     },
104          'AGRAF'           : { 'mode' : 'a', 'driver' : self.ImprTableau,   },
105          'TABLEAU_CROISE'  : { 'mode' : 'a', 'driver' : self.ImprTabCroise, },
106       }
107       kargs={
108          'FICHIER'   : FICHIER,
109          'FORMAT'    : FORMAT,
110          'dform'     : DicForm.copy(),
111          'mode'      : para[FORMAT]['mode'],
112       }
113       if dform != None and type(dform) == DictType:
114          kargs['dform'].update(dform)
115       # ajout des options
116       kargs.update(opts)
117       
118       if not kargs.get('PAGINATION'):
119          # call the associated driver
120          para[FORMAT]['driver'](**kargs)
121
122       else:
123          if not type(kargs['PAGINATION']) in EnumTypes:
124             ppag = [kargs['PAGINATION'],]
125          else:
126             ppag = list(kargs['PAGINATION'])
127          del kargs['PAGINATION']
128          npag = len(ppag)
129          # paramètres hors ceux de la pagination
130          lkeep = [p for p in self.para if ppag.count(p)==0]
131          # création des listes des valeurs distinctes
132          lvd = []
133          for p in ppag:
134             lvp = getattr(self,p).values()
135             lvn = []
136             for it in lvp:
137                if it != None and lvn.count(it) == 0:
138                   lvn.append(it)
139             lvn.sort()
140             lvd.append(lvn)
141          # création des n-uplets
142          s  = '[['+','.join(['x'+str(i) for i in range(npag)])+'] '
143          s += ' '.join(['for x'+str(i)+' in lvd['+str(i)+']' for i in range(npag)])+']'
144          try:
145             lnup = eval(s)
146          except SyntaxError, s:
147             UTMESS('F','Table','Erreur lors de la construction des n-uplets')
148          # pour chaque n-uplet, on imprime la sous-table
149          for nup in lnup:
150             tab = self
151             for i in range(npag):
152                tab = tab & (getattr(tab,ppag[i]) == nup[i])
153                sl = ''
154                if tab.titr: sl='\n'
155                tab.titr += sl+ppag[i]+': '+str(nup[i])
156             tab[lkeep].Impr(**kargs)
157
158 # ------------------------------------------------------------------------------
159    def ImprTableau(self,**kargs):
160       """Impression au format TABLEAU ou ASTER
161       """
162       # fichier ou stdout
163       if kargs.get('FICHIER')<>None:
164          f=open(kargs['FICHIER'],kargs['mode'])
165       else:
166          f=sys.stdout
167       # ecriture
168       f.write(self.ReprTable(**kargs) + '\n')
169       # fermeture
170       if kargs.get('FICHIER')<>None:
171          f.close()
172
173 # ------------------------------------------------------------------------------
174    def ReprTable(self,FORMAT='TABLEAU',dform=None,**ignore):
175       """Représentation d'une Table ou d'une Colonne sous forme d'un tableau.
176       """
177       rows=self.rows
178       para=self.para
179       typ =self.type
180       if not type(para) in EnumTypes:
181          para=[self.para,]
182          typ =[self.type,]
183       if dform==None:
184          dform = DicForm.copy()
185       # est-ce que l'attribut .type est renseigné ?
186       typdef=typ<>[None]*len(typ)
187       txt=[]
188       # ['']+ pour ajouter un séparateur en début de ligne
189       lspa=['',]
190       # lmax : largeur max des colonnes = max(form{K,R,I},len(parametre))
191       lmax=[]
192       for p in para:
193          t=typ[para.index(p)]
194          larg_max=max([len(str(p))] + \
195                [len(FMT(dform,k,t) % 0) for k in ('formK','formR','formI')])
196          lspa.append(FMT(dform,'formK',t,larg_max,str(p)) % p)
197          lmax.append(larg_max)
198       if typdef:
199          stype=dform['csep'].join([''] + \
200           [FMT(dform,'formK',typ[i],lmax[i]) % typ[i] for i in range(len(para))])
201       txt.append(dform['ccom'])
202       txt.append(dform['ccom']+'-'*80)
203       txt.append(dform['ccom'])
204       ASTER=(FORMAT=='ASTER')
205       if ASTER:
206          txt.append('#DEBUT_TABLE')
207       if self.titr:
208          if ASTER:
209             txt.extend(['#TITRE '+lig for lig in self.titr.split('\n')])
210          else:
211             txt.extend([dform['ccom']+lig for lig in self.titr.split('\n')])
212       txt.append(dform['csep'].join(lspa))
213       if ASTER and typdef:
214          txt.append(stype)
215       for r in rows:
216          lig=['']
217          empty=True
218          for v in para:
219             i=para.index(v)
220             t=typ[i]
221             rep=r.get(v,None)
222             if type(rep) is FloatType:
223                lig.append(FMT(dform,'formR',t,lmax[i]) % rep)
224                empty=False
225             elif type(rep) in (IntType, LongType):
226                lig.append(FMT(dform,'formI',t,lmax[i]) % rep)
227                empty=False
228             else:
229                if rep==None:
230                   rep='-'
231                else:
232                   empty=False
233                s=FMT(dform,'formK',t,lmax[i],rep) % str(rep)
234                # format AGRAF = TABLEAU + '\' devant les chaines de caractères !
235                if FORMAT=='AGRAF':
236                   s='\\'+s
237                lig.append(s)
238          if not empty:
239             lig2 = [dform['sepch'].join(ch.splitlines()) for ch in lig]
240             txt.append(dform['csep'].join(lig2))
241       if ASTER:
242          txt.append('#FIN_TABLE')
243       # ajout du debut de ligne
244       if dform['cdeb']<>'':
245          txt=[dform['cdeb']+t for t in txt]
246
247       return dform['cfin'].join(txt)
248 # ------------------------------------------------------------------------------
249    def ImprTabCroise(self,**kargs):
250       """Impression au format TABLEAU_CROISE d'une table ayant 3 paramètres.
251       """
252       # création du tableau croisé et impression au format TABLEAU
253       tabc=self.Croise()
254       kargs['FORMAT']='TABLEAU'
255       tabc.Impr(**kargs)
256 # ------------------------------------------------------------------------------
257    def ImprGraph(self, **kargs):
258       """Impression au format XMGRACE : via le module Graph
259       """
260       args=kargs.copy()
261       if len(self.para) != 2:
262          UTMESS('A','Table','La table doit avoir exactement deux paramètres '\
263                 'pour une impression au format XMGRACE.')
264          return
265       # suppression des lignes contenant une cellule vide
266       tnv = getattr(self, self.para[0]).NON_VIDE() \
267           & getattr(self, self.para[1]).NON_VIDE()
268       # objet Graph
269       graph=Graph.Graph()
270       dicC={
271          'Val' : [getattr(tnv, tnv.para[0]).values(),
272                   getattr(tnv, tnv.para[1]).values()],
273          'Lab' : tnv.para,
274       }
275       if args['LEGENDE']==None: del args['LEGENDE']
276       Graph.AjoutParaCourbe(dicC, args)
277       graph.AjoutCourbe(**dicC)
278       
279       # Surcharge des propriétés du graphique et des axes
280       # (bloc quasiment identique dans impr_fonction_ops)
281       if args.get('TITRE'):            graph.Titre=args['TITRE']
282       if args.get('BORNE_X'):
283                                        graph.Min_X=args['BORNE_X'][0]
284                                        graph.Max_X=args['BORNE_X'][1]
285       if args.get('BORNE_Y'):
286                                        graph.Min_Y=args['BORNE_Y'][0]
287                                        graph.Max_Y=args['BORNE_Y'][1]
288       if args.get('LEGENDE_X'):        graph.Legende_X=args['LEGENDE_X']
289       if args.get('LEGENDE_Y'):        graph.Legende_Y=args['LEGENDE_Y']
290       if args.get('ECHELLE_X'):        graph.Echelle_X=args['ECHELLE_X']
291       if args.get('ECHELLE_Y'):        graph.Echelle_Y=args['ECHELLE_Y']
292       if args.get('GRILLE_X'):         graph.Grille_X=args['GRILLE_X']
293       if args.get('GRILLE_Y'):         graph.Grille_Y=args['GRILLE_Y']
294       
295       try:
296          graph.Trace(**args)
297       except TypeError:
298          UTMESS('A','Table','Les cellules ne doivent contenir que des nombres réels')
299
300 # ------------------------------------------------------------------------------
301 # ------------------------------------------------------------------------------
302 # ------------------------------------------------------------------------------
303 class Table(TableBase):
304    """Une table est construite comme une liste de lignes, chaque ligne est
305    un dictionnaire.
306    On crée puis on ajoute les lignes avec la méthode append :
307       t=Table()
308       t.append(dict(a=1,b=2))
309       t.append(dict(a=3,b=4))
310    La méthode __iter__ définit un itérateur sur les lignes de la table,
311    __repr__ retourne une représentation de la table, utilisée par "print t".
312    Grace à la classe Colonne et à sa méthode _extract, il est possible
313    de construire une sous-table qui satisfait un critère donné.
314    Le critère est donné par une fonction Python qui retourne vrai
315    ou faux si la valeur d'une colonne respecte le critère ou non.
316    Exemple:
317      def critere(valeur):
318          return valeur < 10
319      soustable = t.a._extract(critere)
320    t.a retourne un objet intermédiaire de la classe Colonne qui mémorise
321    le nom de la colonne demandée (a, ici).
322    """
323 # ------------------------------------------------------------------------------
324    def __init__(self, rows=[], para=[], typ=[], titr=''):
325       """Constructeur de la Table :
326          rows : liste des lignes (dict)
327          para : liste des paramètres
328          type : liste des types des paramètres
329          titr : titre de la table
330       """
331       self.rows = [r for r in rows if r.values() != [None]*len(r.values())]
332       self.para = list(para)
333       for i in self.para :
334           if self.para.count(i) != 1 :
335              UTMESS('F','Table','Parametre en double: %s' %i)
336       if len(typ) == len(self.para):
337          self.type = list(typ)
338       else:
339          self.type = [None]*len(self.para)
340       self.titr = titr
341    
342 # ------------------------------------------------------------------------------
343    def copy(self):
344       """Retourne une copie de la table.
345       """
346       rows = []
347       for r in self.rows:
348          rows.append(copy(r))
349       return Table(rows, self.para[:], self.type[:], self.titr)
350
351 # ------------------------------------------------------------------------------
352    def append(self, obj):
353       """Ajoute une ligne (type dict) qui peut éventuellement définir un
354       nouveau paramètre."""
355       para=obj.keys()
356       for p in para:
357          if not p in self.para:
358             self.para.append(p)
359             self.type.append(_typaster(obj[p]))
360          else:
361             ip=self.para.index(p)
362             self.type[ip]=_typaster(obj[p], self.type[ip])
363       self.rows.append(obj)
364
365 # ------------------------------------------------------------------------------
366    def SansColonneVide(self):
367       """Retourne une copie de la table dans laquelle on a supprimé les colonnes
368       vides (les lignes vides sont automatiquement supprimées).
369       """
370       tab = self.copy()
371       lp = tab.para[:]
372       for para in lp:
373          if len(tab[para]) == 0:
374             bid = lp.pop(0)
375       return tab[lp]
376
377 # ------------------------------------------------------------------------------
378    def __setitem__(self, k_para, k_value):
379       """Ajoute une colonne k_para dont les valeurs sont dans k_value"""
380       if len(k_value)==0:
381          return
382       if k_para in self.para :
383          UTMESS('F','Table','(setitem) Le parametre %s existe déjà.' % k_para)
384       self.para.append(k_para)
385       self.type.append(_typaster(k_value[0]))
386       i=0
387       for row in self:
388          if i<len(k_value):
389             row[k_para]=k_value[i]
390             self.type[-1]=_typaster(k_value[i], self.type[-1])
391          else:
392             row[k_para]=None
393          i+=1
394       for j in range(i,len(k_value)): 
395          self.append({k_para:k_value[j]})
396
397 # ------------------------------------------------------------------------------
398    def fromfunction(self, nom_para, funct, l_para=None, const=None):
399       """Ajoute une colonne `nom_para` en évaluant la fonction `funct` sur
400       la valeur des paramètres `l_para` (qui doivent exister dans la table).
401       Si `l_para` n'est pas fourni, on prend `funct`.nompar (FORMULE Aster).
402       On peut passer un dictionnaire de constantes dans `const`. Quand on
403       utilise une FORMULE Aster, les constantes sont prises dans le contexte
404       global.
405       """
406       # vérif préalables
407       if not hasattr(funct, '__call__'):
408          UTMESS('F', 'Table', "(fromfunction) '%s' n'a pas d'attribut '__call__'." \
409             % funct.__name__)
410       if nom_para in self.para :
411          UTMESS('F','Table','Le parametre %s existe déjà.' % nom_para)
412       if l_para == None:
413          if not hasattr(funct, 'nompar'):
414             UTMESS('F', 'Table', "(fromfunction) '%s' n'a pas d'attribut 'nompar'." \
415                % funct.__name__)
416          l_para = funct.nompar
417       if not type(l_para) in EnumTypes:
418          l_para = [l_para]
419       not_found = ', '.join([p for p in l_para if not p in self.para])
420       if not_found != '':
421          UTMESS('F','Table','Parametre(s) absent(s) de la table : %s' % not_found)
422       if const == None:
423          const = {}
424       if type(const) is not DictType:
425          UTMESS('F', 'Table', "L'argument 'const' doit etre de type 'dict'.")
426       # liste des valeurs des paramètres
427       tabpar = []
428       for para in l_para:
429          vals = getattr(self, para).values()
430          tabpar.append(vals)
431       tabpar = transpose.transpose(tabpar)
432       # évaluation de la fonction sur ces paramètres
433       vectval = []
434       for lpar in tabpar:
435          # si un paramètre est absent, on ne peut pas évaluer la formule
436          if None in lpar:
437             vectval.append(None)
438          else:
439             vectval.append(funct(*lpar, **const))
440       # ajout de la colonne
441       self[nom_para] = vectval
442
443 # ------------------------------------------------------------------------------
444    def __iter__(self):
445       """Itère sur les lignes de la Table"""
446       return iter(self.rows)
447
448 # ------------------------------------------------------------------------------
449    def __getattr__(self, column):
450       """Construit un objet intermediaire (couple table, colonne)"""
451       typ=None
452       if not column in self.para:
453          column=''
454       else:
455          typ=self.type[self.para.index(column)]
456       return Colonne(self, column, typ)
457
458 # ------------------------------------------------------------------------------
459    def sort(self, CLES=None, ORDRE='CROISSANT'):
460       """Tri de la table.
461          CLES  : liste des clés de tri
462          ORDRE : CROISSANT ou DECROISSANT
463       """
464       # par défaut, on prend tous les paramètres
465       if CLES == None:
466          CLES = self.para[:]
467       # vérification des arguments
468       if not type(CLES) in EnumTypes:
469          CLES = [CLES]
470       else:
471          CLES = list(CLES)
472       not_found = ', '.join([p for p in CLES if not p in self.para])
473       if not_found != '':
474          UTMESS('F', 'Table', 'Parametre(s) absent(s) de la table : %s' % not_found)
475       if not ORDRE in ('CROISSANT', 'DECROISSANT'):
476          UTMESS('F', 'Table', 'Valeur incorrecte pour ORDRE : %s' % ORDRE)
477       # tri
478       self.rows = sort_table(self.rows, self.para, CLES, (ORDRE=='DECROISSANT'))
479
480 # ------------------------------------------------------------------------------
481    def __delitem__(self, args):
482       """Supprime les colonnes correspondantes aux éléments de args """
483       if not type(args) in EnumTypes:
484          args=[args,]
485       new_rows=self.rows
486       new_para=self.para
487       new_type=self.type
488       for item in args:
489          del new_type[new_para.index(item)]
490          new_para.remove(item)
491          for line in new_rows:
492             del line[item] 
493       return Table(new_rows, new_para, new_type, self.titr)
494
495 # ------------------------------------------------------------------------------
496    def __getitem__(self, args):
497       """Extrait la sous table composée des colonnes dont les paramètres sont dans args """
498       if not type(args) in EnumTypes:
499          args=[args,]
500       else:
501          args=list(args)
502       new_rows=[]
503       new_para=args
504       new_type=[]
505       for item in new_para:
506          if not item in self.para:
507             return Table()
508          new_type.append(self.type[self.para.index(item)])
509       for line in self:
510          new_line={}
511          for item in new_para:
512             new_line[item]=line.get(item)
513          new_rows.append(new_line)
514       return Table(new_rows, new_para, new_type, self.titr)
515
516 # ------------------------------------------------------------------------------
517    def __and__(self, other):
518       """Intersection de deux tables (opérateur &)"""
519       if other.para<>self.para:
520          UTMESS('A','Table','Les paramètres sont différents')
521          return Table()
522       else:
523          tmp = [ r for r in self if r in other.rows ]
524          return Table(tmp, self.para, self.type, self.titr)
525
526 # ------------------------------------------------------------------------------
527    def __or__(self, other):
528       """Union de deux tables (opérateur |)"""
529       if other.para<>self.para:
530          UTMESS('A','Table','Les paramètres sont différents')
531          return Table()
532       else:
533          tmp = self.rows[:]
534          tmp.extend([ r for r in other if r not in self ])
535          return Table(tmp, self.para, self.type[:], self.titr)
536
537 # ------------------------------------------------------------------------------
538    def values(self):
539       """Renvoie la table sous la forme d'un dictionnaire de listes dont les
540       clés sont les paramètres.
541       """
542       dico={}
543       for column in self.para:
544          dico[column]=Colonne(self, column).values()
545       return dico
546
547 # ------------------------------------------------------------------------------
548    def dict_CREA_TABLE(self):
549       """Renvoie le dictionnaire des mots-clés à fournir à la commande CREA_TABLE
550       pour produire une table_sdaster.
551       """
552       dico={ 'TITRE' : ['%-80s' % lig for lig in self.titr.split('\n')],
553              'LISTE' : [], }
554       # remplissage de chaque occurence (pour chaque paramètre) du mot-clé facteur LISTE
555       for i in range(len(self.para)):
556          # nom du paramètre et type si K*
557          d={ 'PARA' : self.para[i], }
558          typ=self.type[i]
559          if typ==None:
560             UTMESS('F', 'Table', 'Type du paramètre %s non défini.' %\
561                    self.para[i])
562          elif typ[0]=='K':
563             mc='LISTE_K'
564             if not typ in ('K8', 'K16', 'K24'):
565                UTMESS('A','Table','Type du paramètre %s forcé à %s' % (self.para[i],Kdef))
566                typ=Kdef
567             d['TYPE_K']=typ
568          elif typ=='I':
569             mc='LISTE_I'
570          elif typ=='R':
571             mc='LISTE_R'
572          # valeurs sans trou / avec trou
573          vals=getattr(self, self.para[i]).values()
574          if vals.count(None)==0:
575             d[mc]=vals
576          else:
577             d['NUME_LIGN'] = [j+1 for j in range(len(vals)) if vals[j]<>None]
578             d[mc]          = [v   for v in vals             if v      <>None]
579          if len(d[mc])==0:
580             UTMESS('I','Table','Colonne %s vide' % self.para[i])
581          else:
582             dico['LISTE'].append(d)
583       if len(dico['LISTE'])==0:
584          UTMESS('F','Table','La table est vide')
585       return dico
586
587 # ------------------------------------------------------------------------------
588    def Array(self,Para,Champ):
589       """Renvoie sous forme de NumArray le résultat d'une extraction dans une table
590       méthode utile à macr_recal
591       """
592       import Numeric
593       __Rep = self[Para,Champ].values()
594       F = Numeric.zeros((len(__Rep[Para]),2), Numeric.Float)
595       for i in range(len(__Rep[Para])):
596          F[i][0] = __Rep[Para][i]
597          F[i][1] = __Rep[Champ][i]
598       del(__Rep)
599       return F
600
601 # ------------------------------------------------------------------------------
602    def Croise(self):
603       """Retourne un tableau croisé P3(P1,P2) à partir d'une table ayant
604       trois paramètres (P1, P2, P3).
605       """
606       if len(self.para)<>3:
607          UTMESS('A', 'Table', 'La table doit avoir exactement trois paramètres.')
608          return Table()
609       py, px, pz = self.para
610       ly, lx, lz = [getattr(self,p).values() for p in self.para]
611       new_rows=[]
612       #lpz='%s=f(%s,%s)' % (pz,px,py)
613       lpz='%s/%s' % (px,py)
614       new_para=[lpz,]
615       # attention aux doublons dans lx et ly
616       for it in ly:
617          if it<>None and new_para.count(it)==0:
618             new_para.append(it)
619       newx=[]
620       for it in lx:
621          if it<>None and newx.count(it)==0:
622             newx.append(it)
623       for x in newx:
624          if x<>None:
625             d={ lpz : x, }
626             taux = (getattr(self,px)==x)
627             for dz in taux.rows:
628                d[dz[py]]=dz[pz]
629             new_rows.append(d)
630       new_type=[self.type[0],] + [self.type[2]]*len(ly)
631       new_titr=self.titr
632       if new_titr<>'': new_titr+='\n'
633       new_titr+=pz + ' FONCTION DE ' + px + ' ET ' + py
634       return Table(new_rows, new_para, new_type, new_titr)
635
636 # ------------------------------------------------------------------------------
637    def Renomme(self, pold, pnew):
638       """Renomme le paramètre `pold` en `pnew`.
639       """
640       if not pold in self.para:
641          raise KeyError, 'Paramètre %s inexistant dans cette table' % pold
642       elif self.para.count(pnew)>0:
643          raise KeyError, 'Le paramètre %s existe déjà dans la table' % pnew
644       else:
645          self.para[self.para.index(pold)] = pnew
646          for lig in self:
647             lig[pnew] = lig[pold]
648             del lig[pold]
649
650 # ------------------------------------------------------------------------------
651 # ------------------------------------------------------------------------------
652 # ------------------------------------------------------------------------------
653 class Colonne(TableBase):
654    """Classe intermédiaire pour mémoriser un couple (table, nom de colonne)
655    et exprimer les critères d'extraction sous une forme naturelle en python
656    en surchargeant les operateurs <, >, <> et =.
657    Alors on peut écrire la requete simple :
658      soustable=t.a<10
659    Ainsi que des requetes plus complexes :
660      soustable=t.a<10 and t.b <4
661    ou
662      soustable=t.a<10 or t.b <4
663    Les "alias" EQ, NE, LE, LT, GE, GT permettent à la macro IMPR_TABLE
664    d'utiliser directement le mot-clé utilisateur CRIT_COMP défini dans le
665    catalogue : getattr(Table,CRIT_COMP).
666    """
667 # ------------------------------------------------------------------------------
668    def __init__(self, table, column, typ=None):
669       """Constructeur (objet Table associé, paramètre de la colonne, type du
670       paramètre).
671       """
672       self.Table=table
673       self.rows=self.Table.rows
674       self.para=column
675       self.type=typ
676       self.titr=''
677
678 # ------------------------------------------------------------------------------
679    def _extract(self, fun):
680       """Construit une table avec les lignes de self.Table 
681          dont l'élément de nom self.para satisfait le critère fun,
682          fun est une fonction qui retourne vrai ou faux
683       """
684       return Table([row for row in self.Table if fun(row.get(self.para))], self.Table.para, self.Table.type, self.Table.titr)
685
686 # ------------------------------------------------------------------------------
687    def __le__(self, VALE):
688       return self._extract(lambda v: v<>None and v<=VALE)
689
690 # ------------------------------------------------------------------------------
691    def __lt__(self, VALE):
692       return self._extract(lambda v: v<>None and v<VALE)
693
694 # ------------------------------------------------------------------------------
695    def __ge__(self, VALE):
696       return self._extract(lambda v: v<>None and v>=VALE)
697
698 # ------------------------------------------------------------------------------
699    def __gt__(self, VALE):
700       return self._extract(lambda v: v<>None and v>VALE)
701
702 # ------------------------------------------------------------------------------
703    def __eq__(self, VALE, CRITERE='RELATIF', PRECISION=0.):
704       if type(VALE) in EnumTypes :
705          return self._extract(lambda v: v in VALE)
706       if PRECISION==0. or not type(VALE) in NumberTypes:
707          if type(VALE) in StringTypes:
708             return self._extract(lambda v: v<>None and str(v).strip()==VALE.strip())
709          else:
710             return self._extract(lambda v: v==VALE)
711       else:
712          if CRITERE=='ABSOLU':
713             vmin=VALE-PRECISION
714             vmax=VALE+PRECISION
715          else:
716             vmin=(1.-PRECISION)*VALE
717             vmax=(1.+PRECISION)*VALE
718          return self._extract(lambda v: v<>None and vmin<v<vmax)
719
720 # ------------------------------------------------------------------------------
721    def REGEXP(self, regexp):
722       """Retient les lignes dont le paramètre satisfait l'expression
723       régulière `regexp`.
724       """
725       if not type(regexp) in StringTypes:
726          return self._extract(lambda v : False)
727       return self._extract(lambda v : v != None and re.search(regexp, v) != None)
728
729 # ------------------------------------------------------------------------------
730    def __ne__(self, VALE, CRITERE='RELATIF', PRECISION=0.):
731       if type(VALE) in EnumTypes :
732          return self._extract(lambda v: v not in VALE)
733       if PRECISION==0. or not type(VALE) in NumberTypes:
734          if type(VALE) in StringTypes:
735             return self._extract(lambda v: v<>None and str(v).strip()<>VALE.strip())
736          else:
737             return self._extract(lambda v: v<>VALE)
738       else:
739          if CRITERE=='ABSOLU':
740             vmin=VALE-PRECISION
741             vmax=VALE+PRECISION
742          else:
743             vmin=(1.-PRECISION)*VALE
744             vmax=(1.+PRECISION)*VALE
745          return self._extract(lambda v: v<>None and (v<vmin or vmax<v))
746
747 # ------------------------------------------------------------------------------
748    def MAXI(self):
749       # important pour les performances de récupérer le max une fois pour toutes
750       maxi=max(self)
751       return self._extract(lambda v: v==maxi)
752
753 # ------------------------------------------------------------------------------
754    def MINI(self):
755       # important pour les performances de récupérer le min une fois pour toutes
756       mini=min(self)
757       return self._extract(lambda v: v==mini)
758
759 # ------------------------------------------------------------------------------
760    def ABS_MAXI(self):
761       # important pour les performances de récupérer le max une fois pour toutes
762       abs_maxi=max([abs(v) for v in self.values() if type(v) in NumberTypes])
763       return self._extract(lambda v: v==abs_maxi or v==-abs_maxi)
764
765 # ------------------------------------------------------------------------------
766    def ABS_MINI(self):
767       # important pour les performances de récupérer le min une fois pour toutes
768       abs_mini=min([abs(v) for v in self.values() if type(v) in NumberTypes])
769       # tester le type de v est trop long donc pas de abs(v)
770       return self._extract(lambda v: v==abs_mini or v==-abs_mini)
771
772 # ------------------------------------------------------------------------------
773    def __iter__(self):
774       """Itère sur les éléments de la colonne"""
775       for row in self.Table:
776          # si l'élément n'est pas présent on retourne None
777          yield row.get(self.para)
778          #yield row[self.para]
779
780 # ------------------------------------------------------------------------------
781    def __getitem__(self, i):
782       """Retourne la ième valeur d'une colonne"""
783       return self.values()[i]
784
785 # ------------------------------------------------------------------------------
786    def values(self):
787       """Renvoie la liste des valeurs"""
788       return [r.get(self.para,None) for r in self.Table]
789
790    def not_none_values(self):
791       """Renvoie la liste des valeurs non 'None'"""
792       return [val for val in self.values() if val != None]
793
794 # ------------------------------------------------------------------------------
795    # équivalences avec les opérateurs dans Aster
796    LE=__le__
797    LT=__lt__
798    GE=__ge__
799    GT=__gt__
800    EQ=__eq__
801    NE=__ne__
802    def VIDE(self):
803       return self.__eq__(None)
804    def NON_VIDE(self):
805       return self.__ne__(None)
806
807 # ------------------------------------------------------------------------------
808 # ------------------------------------------------------------------------------
809 # ------------------------------------------------------------------------------
810 def sort_table(rows, l_para, w_para, reverse=False):
811    """Sort list of dict.
812       rows     : list of dict
813       l_para   : list of the keys of dict
814       w_para   : keys of the sort
815    """
816    c_para=[i for i in l_para if i not in w_para]
817    new_rows=rows
818    # rename sort keys by "__" + number + para
819    # ("__" to avoid conflict with existing parameters)
820    for i in w_para :
821       new_key= '__'+str(w_para.index(i))+i
822       for row in new_rows :
823          row[new_key]=row[i]
824          del row[i]
825    # rename others parameters by "___" + para
826    # ("___" to be after sort keys)
827    for i in c_para :
828       new_key= '___'+i
829       for row in new_rows :
830          row[new_key]=row[i]
831          del row[i]
832    # sort
833    new_rows.sort()
834    # reversed sort
835    if reverse:
836       new_rows.reverse()
837    for i in w_para :
838       old_key= '__'+str(w_para.index(i))+i
839       for row in new_rows :
840          row[i]=row[old_key]
841          del row[old_key]
842    for i in c_para :
843       old_key= '___'+i
844       for row in new_rows :
845          row[i]=row[old_key]
846          del row[old_key]
847    return new_rows
848
849 # ------------------------------------------------------------------------------
850 def FMT(dform, nform, typAster=None, larg=0, val=''):
851    """Retourne un format d'impression Python à partir d'un type Aster ('R','I',
852    'K8', 'K16'...). Si typAster==None, retourne dform[nform].
853       larg : largeur minimale du format (val permet de ne pas ajouter des blancs
854       si la chaine à afficher est plus longue que le format, on prend le partie
855       de ne pas tronquer les chaines)
856    """
857    if typAster==None:
858       fmt=dform[nform]
859    elif typAster in ('I', 'R'):
860       if nform=='formK':
861          # convertit %12.5E en %-12s
862          fmt=re.sub('([0-9]+)[\.0-9]*[diueEfFgG]+','-\g<1>s',dform['form'+typAster])
863       else:
864          fmt=dform[nform]
865    else:
866       # typAster = Kn
867       fmt='%-'+typAster[1:]+'s'
868    # on ajoute éventuellement des blancs pour atteindre la largeur demandée
869    if larg<>0:
870       fmt=' '*max(min(larg-len(val),larg-len(fmt % 0)),0) + fmt
871    return fmt
872
873 # ------------------------------------------------------------------------------
874 def merge(tab1, tab2, labels=[]):
875    """Assemble les deux tables tb1 et tb2 selon une liste de labels communs.
876       Si labels est vide:
877        - les lignes de tb2 sont ajoutés à celles de tb1,
878       sinon :
879        - si on trouve les valeurs de tb2 sur les labels dans tb1 (et une seule fois),
880          on surcharge tb1 avec les lignes de tb2 ;
881        - sinon on ajoute la ligne de tb2 à la fin de tb1.
882    """
883    tb1 = tab1.copy()
884    tb2 = tab2.copy()
885    if type(labels) not in EnumTypes:
886       labels=(labels,)
887    for key in labels :
888        if key not in tb1.para : UTMESS('F','Table','Erreur, label non présent %s' % key)
889        if key not in tb2.para : UTMESS('F','Table','Erreur, label non présent %s' % key)
890    # ensemble des paramètres et des types
891    n_para=tb1.para[:]
892    n_type=tb1.type[:]
893    for i in tb2.para:
894       if i not in tb1.para:
895          n_para.append(i)
896          n_type.append(tb2.type[tb2.para.index(i)])
897    # restriction des lignes aux labels communs (peu cher en cpu)
898    rows1 = tb1.rows
899    dlab1 = {}
900    for i1 in range(len(rows1)):
901       tu1 = tuple(map(rows1[i1].__getitem__, labels))
902       if dlab1.get(tu1, '') == '':
903          dlab1[tu1] = i1
904       else:
905          dlab1[tu1] = None
906    # restriction des lignes aux labels communs (peu cher en cpu)
907    rows2 = tb2.rows
908    dlab2 = {}
909    for i2 in range(len(rows2)):
910       tu2 = tuple(map(rows2[i2].__getitem__, labels))
911       if dlab2.get(tu2, '') == '':
912          dlab2[tu2] = i2
913       else:
914          dlab2[tu2] = None
915    # creation de dic1 : dictionnaire de correspondance entre les 
916    # lignes a merger dans les deux tableaux
917    dic1 = {}
918    for cle in dlab1.keys():
919       if dlab1[cle] == None or cle == ():
920          bid = dlab1.pop(cle)
921    for cle in dlab2.keys():
922       if dlab2[cle] == None or cle == ():
923          bid = dlab2.pop(cle)
924    for cle in dlab2.keys():
925       if dlab1.has_key(cle):
926          dic1[dlab2[cle]] = dlab1[cle]
927    # insertion des valeurs de tb2 dans tb1 quand les labels sont communs
928    # (et uniques dans chaque table) OU ajout de la ligne de tb2 dans tb1
929    i2 = -1
930    for r2 in rows2:
931       i2 += 1
932       try:
933          rows1[dic1[i2]].update(r2)
934       except KeyError:
935          rows1.append(r2)
936    # concaténation des titres + info sur le merge
937    tit = '\n'.join([tb1.titr, tb2.titr, 'MERGE avec labels=%s' % repr(labels)])
938    return Table(rows1, n_para, n_type, tit)
939
940 # ------------------------------------------------------------------------------
941 def _typaster(obj, prev=None, strict=False):
942    """Retourne le type Aster ('R', 'I', Kdef) correspondant à l'objet obj.
943    Si prev est fourni, on vérifie que obj est du type prev.
944    Si strict=False, on autorise que obj ne soit pas du type prev s'ils sont
945    tous les deux numériques ; dans ce cas, on retourne le "type enveloppe" 'R'.
946    """
947    dtyp={
948       IntType    : 'I',
949       FloatType  : 'R',
950       StringType : Kdef, UnicodeType : Kdef,
951       NoneType   : 'I',
952    }
953    if type(obj) in dtyp.keys():
954       typobj=dtyp[type(obj)]
955       if prev in [None, typobj]:
956          return typobj
957       elif strict:   # prev<>None et typobj<>prev et strict
958          raise TypeError, "La valeur %s n'est pas de type %s" % (repr(obj),repr(prev))
959       elif prev in ('I','R') and typobj in ('I','R'):
960          return 'R'
961       else:
962          raise TypeError, "La valeur %s n'est pas compatible avec le type %s" \
963                % (repr(obj),repr(prev))
964    else:
965       raise TypeError, 'Une table ne peut contenir que des entiers, réels ' \
966                        'ou chaines de caractères.'