#!/usr/bin/env python # -*- encoding:utf8 -*- # ########################################################################### # Copyright (C) 2006-2007 - Håvard Dahle # # # Lisens: GPL2 # # $Id: 0.6 $ # # Dette skriptet konverterer fra kontoutskrifter lastet ned fra Skandiabanken # til det mer generelle, de facto utvekslingsformatet QIF. QIF kan importeres # til KMyMoney, GnuCash, Microsoft Money, Quicken, Cashbox, etc. # # Bruk det slik: # sb2qif.py [-format] ,... > skandiabanken.qif # # eksempel: # sb2qif.py -cashbox 9xxxxxx_200x_xxx.CSV 9xxxxxx_200x_xxx.CSV > skandiabanken_200x.qif # # QIF filformat: # QIF er et rimelig skjørt og udokumentert filformat. Dette skriptet lager # QIF-filer som fungerer fint med KMyMoney, men det kan feile i andre program. # # Mer om QIF: # http://www.respmech.com/mym2qifw/qif_new.htm # # ########################################################################### import sys, os.path, re, md5, time from StringIO import StringIO __doc__ = u""" Skript som oversetter fra kontoutskrifter i CSV-format til QIF. Håvard Dahle (C) 2006-2007 Bruk: sb2qif.py [-format] ,... > skandiabanken.qif Hvor -format er 'kmymoney' (default) eller 'cashbox' (flere formater mottas med takk). """ class qifskriver: transaksjonstyper = {'E':[], 'I':[]} filkart = {} # egenskaper som kan tilpasses for hvert eksportformat -- i utgangspunktet funker det med kmymoney skrivBalanse = True # Skriv "Opening balance" på toppen av qif-fila balanseFormat = """!Type:Bank D%(aar)s-01-01 POpening Balance T0.00 CX L[konto%(konto)s] ^ """ skrivKategorier = True # Skriv liste over transaksjonskategorier på toppen av qif-fila datoFormat = "%(aar)s-%(mnd)s-%(dag)s" # hvordan skal dater presenteres utgiftFormat = "(%s)" # hvordan skal negative tall angis transaksjonFormat = """D%(dato)s T%(belop)s P%(kategori)s N%(referanse)s M%(tekst)s L%(transaksjonstype)s #%(id)s ^ """ def __init__ (self, frafiler): self.filer = frafiler for f in frafiler: konto, aar = self._analyser_filnavn(f) if not self.filkart.has_key(konto): self.filkart[konto] = {} if not self.filkart[konto].has_key(aar): self.filkart[konto][aar] = {'buf':None, 'inn':[]} self.filkart[konto][aar]['inn'].append(f) def konverter(self, tilfil=None): if tilfil is None: til = sys.stdout else: til = open(tilfil, "w") for konto in self.filkart.keys(): for aar in self.filkart[konto].keys(): self.filkart[konto][aar]['buf'] = StringIO() for f in self.filkart[konto][aar]['inn']: self._konv(f, self.filkart[konto][aar]['buf']) if self.skrivKategorier: til.write(self._list_kategorier()) # list transaksjonskategorier if self.skrivBalanse: # skriv balanse for konto in self.filkart.keys(): for aar in self.filkart[konto].keys(): self.filkart[konto][aar]['buf'].seek(0) til.write(self.balanseFormat % locals()) til.write(self.filkart[konto][aar]['buf'].read()) def _list_kategorier(self): s = "!Type:Cat\n" for innkat in self.transaksjonstyper['I']: s += "N%s\nI\n^\n" % innkat for utkat in self.transaksjonstyper['E']: s += "N%s\nE\n^\n" % utkat return s def konverter_ny(self): base, etter = os.path.splitext(self.filnavn) nyfil = "/tmp/" + base + ".qif" return self.konverter(nyfil) def konverter_fil(self, tilfil=None): if tilfil is None: til = sys.stdout #else: #til = open(tilfil, "w") self._konv(til) def _analyser_transaksjon(self, tr, inntekt): if inntekt: y = "I" else: y = "E" if self.transaksjonstyper[y].count(tr): return self.transaksjonstyper[y].append(tr) def _analyser_skilletegn(self, linje): #BOKF�INGSDATO";"RENTEDATO";"BRUKSDATO";"ARKIVREFERANSE";"TYPE";"TEKST";"UT FRA KONTO";"INN P�KON for z in ('\t', ';', ','): if len(linje.split(z)) == 8: return z raise "Kan ikke finne skilletegn" def _analyser_filnavn(self, filnavn): #97101163680_2004_nov.CSV filnavn = os.path.basename(filnavn) base, etter = os.path.splitext(filnavn) mnder = ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'] if base[0:11].isdigit(): kontonr = base[0:11] u_aar = base[12:16] #u_mnd = base[17:20] #utskriftsdato = "%s-%s-01" % (u_aar, mnder.index(u_mnd) + 1) #return utskriftsdato, kontonr return kontonr, u_aar else: #return "2001-01-01", "konto" return "xxxxxxxxxxxx", "2001-01-01" def _konv(self, innfil, utfil, modus='csv'): inntegnsett = "latin1" uttegnsett = "utf8" f = file(innfil) topp = f.readline() skilletegn = self._analyser_skilletegn(topp) for linje in f: #BOKF�INGSDATO";"RENTEDATO";"BRUKSDATO";"ARKIVREFERANSE";"TYPE";"TEKST";"UT FRA KONTO";"INN P�KON #"2005-05-09";"2005-05-09";"09.05.2005";"93070628";"Overfrsel";"UTLBET; ID 9710022000266641";245,73; try: bokdato, rentedato, bruksdato, ref, _type, tekst, ut, inn = linje.decode(inntegnsett).encode(uttegnsett).split(skilletegn) except ValueError: raise linje #pass ## TODO NBNBN XXXX dato = self._strip(bokdato) kategori = self._strip(tekst) transaksjonstype = self._strip(_type) #if kategori[0:4] in ('FRA-','TIL-'): # betalingsmottaker / betaler finnes i teksten #kategori = kategori[5:] #bet = re.match(r'^(.*)BETNR-\ \d+$', kategori) #try: kategori = bet.group(1) #except AttributeError: pass betx = re.match(r'^([A-Z ]+)?(FRA|TIL)-\ (.+)(BETNR-\ \d+)?$', kategori) if betx: kategori = betx.group(3) try: kategori = re.match(r'(.+) BETNR-\ \d+$', kategori).group(1) except AttributeError: pass if transaksjonstype.lower() in ('overførsel', 'overføring'): # er betalingsoverførsel # se om det er verdig informasjon å bruke som betalingspart # finn ut retning på overføringen if ut: transaksjonstype += " ut" else: transaksjonstype += " inn" if 'mellom egne konti' in kategori.lower(): # egen overføring kategori = "Intern overføring" if transaksjonstype.lower().startswith("visa") and re.match(r'^\d{6}\ .*$', kategori): #VISAkortnummer finnes i teksten kortnr = kategori[0:6] ref = self._strip(ref) + " VISA/%s" % kortnr kategori = kategori[7:] if re.match(r'^\d\d\.\d\d', kategori): #bruksdato finnes i teksten dag = kategori[0:2] mnd = kategori[3:5] aar = dato[0:4] if mnd == "12" and bokdato.split("-")[1] == "01": # transaksjonen var forrige år aar = str(int(aar)-1) #dato = "%s-%s-%s" % (aar, mnd, dag) dato = self.datoFormat % locals() #(aar, mnd, dag) kategori = kategori[5:].strip() if transaksjonstype.lower().startswith("visa") and not ut: # penger er satt inn på visakontoen transaksjonstype += " innskudd" valx = re.match(r'^([A-Z]{3})\ (\d+,\d\d)\ (.*)$', kategori) #Valutainfo finnes i teksten if valx: transaksjonstype += ":" + valx.group(1) #hva skal vi gjøre med valutabeløpet? #valutamengde = valx.group(2) kategori = valx.group(3) if transaksjonstype.lower().startswith("visa") and kategori[0:2] in ('S*', 'SÆ', 'M*', 'MÆ'): # visa-transaksjonen er varesalg (hva betyr 'Mx'?) transaksjonstype = transaksjonstype + "/" + kategori[0:1] kategori = kategori[2:] if re.match(r'^\d{4}\.\d{2}\.\d{5}$', kategori): #teksten er bare et kontonummer kategori = "FRA KONTONUMMER " + kategori # siste test: har noen av feltene blitt tomme? if len(kategori.strip()) == 0: kategori = "Ukjent" if ut: belop = self.utgiftFormat % self._penger(ut) else: belop = self._penger(inn.strip()) utfil.write(self.transaksjonFormat % \ {'dato':dato, 'belop':belop, 'kategori':kategori, 'referanse':self._strip(ref), 'tekst':self._strip(tekst), 'transaksjonstype':transaksjonstype, 'id':self._id(dato, tekst)}) self._analyser_transaksjon(transaksjonstype, inntekt=bool(inn.strip())) def _id(self, dato, tekst): # lag en unik id _id = ''.join([str(ord(z)) for z in tekst.replace(" ", "")]) return "%s-%s-%s" % (dato, _id, time.time()) def _strip(self, s): s = s.strip() if s[0] in ('"', "'"): s = s[1:] if s[-1] in ('"', "'"): s = s[:-1] if not s: return "" if s[0] == "*": try: s = s[1:] except IndexError: return "" s = s.replace(":", "-") return s def _penger(self, p): # bytt til engelsk desimaltegn etc mx = re.match(r'^(\d+),(\d\d)$', p) try: p = "%s.%s" % (mx.group(1), mx.group(2)) except AttributeError: pass return p class cashbox(qifskriver): """Cashboxs qif-format, dekompilert av Christopher Campbell Jensen x Ingen 'Open Balance', bare '!Type:Bank' på toppen av fila x Ingen liste over transaksjonskategorier x Datoformat på typen DD/MM/ÅÅÅÅ x Negative tall skrives -1.0 x Transaksjonene har ikke kategori Denne klassen kan brukes som mal for nye formater. Subklass 'qifskriver' og sett i gang. """ skrivBalanse = True # Skriv "Opening balance" på toppen av qif-fila balanseFormat = "!Type:Bank\n\n" skrivKategorier = False # Skriv liste over transaksjonskategorier på toppen av qif-fila datoFormat = "%(dag)s/%(mnd)s/%(aar)s" # hvordan skal dater presenteres utgiftFormat = "-%s" # hvordan skal negative tall angis transaksjonFormat = """D%(dato)s T%(belop)s M%(tekst)s N%(referanse)s P%(kategori)s - %(transaksjonstype)s #%(id)s ^ """ kmymoney = qifskriver ## Kmymoneys qif-forståelse er default utputt if __name__ == '__main__': if not sys.argv[1:]: print __doc__ sys.exit() if sys.argv[1][1:] in ('cashbox','kmymoney'): qiffer = locals()[sys.argv[1][1:]] sys.argv.pop(1) else: qiffer = qifskriver konv = qiffer(sys.argv[1:]) konv.konverter()