Outils pour utilisateurs

Outils du site


lecture_dbase

Ceci est une ancienne révision du document !


Lecture d'un fichier dbase III

Objectif

J'avais un problème très concret à résoudre: j'ai un programme écrit dans un langage que je ne connais pas bien, qui crée et met à jour des fichiers dbase III, et je voulais pouvoir lire ces fichiers sous Python pour faire des exploitations non prévues à l'origine.

Je me suis inspiré de 2 sites:

Je me suis limité à dbase III, mais le 2ème site cité permet de modifier le code de lecture en fonction de la version dbase utilisée.

Code proposé

J'ai choisi de faire une classe, parce que ça permet non seulement de lire les données, mais aussi de conserver les valeurs d'entête (les noms des champs par exemple) pour les traitements suivants.

import os
import struct, datetime
 
class DBF(object):
 
    def __init__(self, nfc):
 
        # paramètres accessibles de l'extérieur:
        self.nfc = nfc  # => nom du fichier complet (avec son chemin)
        self.nf = os.path.split(self.nfc)[1]  # => nom du fichier (sans chemin)
        # self.version => version dbase
        # self.date => dernière date de mise à jour
        # self.nbenreg => nombre d'enregistrements
        # self.nbchamps => nombre de champs
        # self.lgenreg => longueur en octets de chaque enregistrement
        # self.noms => liste des noms de champs
        # self.types => liste des types de champs
        # self.donnees => liste des enregistrements du fichier
 
 
        # === ouverture du fichier .dbf =====================================
        f = open(self.nfc, 'rb')
 
        # === Lecture de l'entete ===========================================
        self.version, AA, MM, JJ, self.nbenreg, lgentete, self.lgenreg = \
                                     struct.unpack('<BcccLHH20x', f.read(32))
        self.date = "%02d/%02d/%4d" % (ord(JJ),ord(MM),ord(AA)+1900)
        self.nbchamps = (lgentete - 33) // 32
 
        champs = []
        for numchamp in xrange(0, self.nbchamps):
            nom, typechamp, lgchamp, deci = \
                                     struct.unpack('<11sc4xBB14x', f.read(32))
            nom = nom.replace('\0', '')
            champs.append((nom, typechamp, lgchamp, deci))
        self.noms = [champ[0] for champ in champs]
        self.types = [champ[1:] for champ in champs]
 
        terminator = f.read(1)
 
        # === Lecture des données ============================================
 
        # calcul du nombre réel d'enregistrement à lire (y compris effacés)
        courant = f.tell()
        f.seek(0,2)
        fin = f.tell()-1
        nbe = (fin-courant)//self.lgenreg
        f.seek(courant)
 
        fmt = '1s' + ''.join(['%ds' % info[1] for info in self.types])
        self.donnees = []
        for i in xrange(nbe):
            record = list(struct.unpack(fmt, f.read(self.lgenreg)))
            if record.pop(0) != ' ':
                continue         # = c'est un enregistrement efface
 
            # ajustement de chaque valeur selon le type de champs
            enreg = []
            for j in xrange(0, self.nbchamps):
                valeur = record[j].strip()
                if self.types[j][0] == 'N':
                    valeur = valeur.replace('\0', '').lstrip()
                    if valeur == '':
                        valeur = 0
                    elif self.types[j][2]!=0:
                        valeur = float(valeur)
                    else:
                        valeur = int(valeur)
                elif self.types[j][0] == 'D':
                    y, m, d = int(valeur[:4]), int(valeur[4:6]), int(valeur[6:8])
                    valeur = datetime.date(y, m, d)
                elif self.types[j][0] == 'L':
                    valeur = (valeur in 'YyTt' and 'T') or (valeur in 'NnFf' and 'F') or '?'
 
                enreg.append(valeur)
 
            self.donnees.append(enreg)
 
        f.close()

Utilisation

Voilà un exemple d'utilisation: lecture d'un fichier appelé ici: stats.dbf

nfc = r"C:\Python25\Pydev\dbase\base\stats.dbf"
 
dbf = DBF(nfc)
 
print "="*50
print u"fichier=", dbf.nf
print u"avec chemin=", dbf.nfc
print u"version=", dbf.version
print u"date de dernière mise à jour=", dbf.date
print u"nombre d'enregistrements=", dbf.nbenreg
print u"nombre de champs", dbf.nbchamps
print u"longueur de chaque enregistrement", dbf.lgenreg
print u"noms des champs=", dbf.noms
print u"types des champs", dbf.types
for i, data in enumerate(dbf.donnees):
    print i, data

Voilà ce que ça donne (affichage des 10 premiers enregistrements)

fichier= stats.dbf
avec chemin= C:\Python25\Pydev\dbase\base\stats.dbf
version= 3
date de dernière mise à jour= 02/11/2008
nombre d'enregistrements= 41
nombre de champs 9
longueur de chaque enregistrement 106
noms des champs= ['NATION', 'NAUTR', 'NPHR', 'NAUTX', 'NPHX', 'CAUTR', 'CPHR', 'CAUTX', 'CPHX']
types des champs [('C', 25, 0), ('N', 10, 0), ('N', 10, 0), ('N', 10, 0), ('N', 10, 0), ('N', 10, 0), ('N', 10, 0), ('N', 10, 0), ('N', 10, 0)]
0 ["Allemagne",23,76,5,8,30,98,11,16]
1 ["Andorre",0,0,0,0,1,3,0,0]
2 ["Argentine",3,12,2,4,3,12,1,2]
3 ["Australie",2,8,0,0,2,8,0,0]
4 ["Autriche",1,4,0,0,0,0,0,0]
5 ["Belgique",22,87,12,20,18,71,10,20]
6 ["Bosnie-Herzégovine",1,4,0,0,1,4,0,0]
7 ["Canada",0,0,0,0,1,4,0,0]
8 ["Chine",19,76,6,7,38,152,2,2]
9 ["Chypre",0,0,0,0,1,4,0,0]
10 ["Croatie",2,8,0,0,2,8,0,0]

Bien sûr, pour afficher des données avec des accents, il faut utiliser des fonctions de décodage/encodage. Pour cet exemple, les données étaient stockées en 'cp850', et j'ai imprimé en code compatible avec le périphérique de sortie. Ainsi, si la chaine est x, il faut afficher:

x.decode('cp850').encode(sys.stdout.encoding)

Perspective de développement

Je n'avais pas besoin d'écrire le fichier dbase modifié (à cause des index) et donc, je n'ai pas développé l'écriture, mais le 1er site web cité permet de le faire.

Sinon, on peut, selon les traitements à faire, ajouter des fonctions de tri, de sélection, et de traitement croisés entre plusieurs bases ainsi lues (intersection, union, etc…)

Pour tout ce qui concerne les tris et recherches dans des fichiers ayant des accents, n'oubliez pas qu'il existe des solutions sur ce site!

lecture_dbase.1232095086.txt.gz · Dernière modification: 2009/01/16 09:38 de tyrtamos

Outils de la page