summaryrefslogtreecommitdiff
path: root/ofxparse/ofxparse.py
diff options
context:
space:
mode:
Diffstat (limited to 'ofxparse/ofxparse.py')
-rw-r--r--ofxparse/ofxparse.py349
1 files changed, 187 insertions, 162 deletions
diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py
index 18b9667..2621286 100644
--- a/ofxparse/ofxparse.py
+++ b/ofxparse/ofxparse.py
@@ -1,4 +1,4 @@
-from __future__ import absolute_import, with_statement
+from __future__ import absolute_import
import sys
import decimal
@@ -14,45 +14,18 @@ except ImportError:
from io import StringIO
import six
-
-if 'OrderedDict' in dir(collections):
- odict = collections
-else:
- import ordereddict as odict
-
from . import mcc
+odict = collections
-def skip_headers(fh):
- '''
- Prepare `fh` for parsing by BeautifulSoup by skipping its OFX
- headers.
- '''
- if fh is None or isinstance(fh, six.string_types):
- return
- fh.seek(0)
- header_re = re.compile(r"^\s*\w+:\s*\w+\s*$")
- while True:
- pos = fh.tell()
- line = fh.readline()
- if not line:
- break
- if header_re.search(line) is None:
- fh.seek(pos)
- return
-
+try:
+ from bs4 import BeautifulSoup
-def soup_maker(fh):
- skip_headers(fh)
- try:
- from bs4 import BeautifulSoup
- soup = BeautifulSoup(fh, "html.parser")
- for tag in soup.findAll():
- tag.name = tag.name.lower()
- except ImportError:
- from BeautifulSoup import BeautifulStoneSoup
- soup = BeautifulStoneSoup(fh)
- return soup
+ def soup_maker(fh):
+ return BeautifulSoup(fh, 'html.parser')
+except ImportError:
+ from BeautifulSoup import BeautifulStoneSoup
+ soup_maker = BeautifulStoneSoup
def try_decode(string, encoding):
@@ -94,6 +67,12 @@ class OfxFile(object):
if not hasattr(self.fh, "seek"):
return # fh is not a file object, we're doomed.
+ # If the file handler is text stream, convert to bytes one:
+ first = self.fh.read(1)
+ self.fh.seek(0)
+ if not isinstance(first, bytes):
+ self.fh = six.BytesIO(six.b(self.fh.read()))
+
with save_pos(self.fh):
self.read_headers()
self.handle_encoding()
@@ -174,14 +153,14 @@ class OfxPreprocessedFile(OfxFile):
# find all closing tags as hints
closing_tags = [t.upper() for t in re.findall(r'(?i)</([a-z0-9_\.]+)>',
- ofx_string)]
+ ofx_string)]
# close all tags that don't have closing tags and
# leave all other data intact
last_open_tag = None
tokens = re.split(r'(?i)(</?[a-z0-9_\.]+>)', ofx_string)
new_fh = StringIO()
- for idx, token in enumerate(tokens):
+ for token in tokens:
is_closing_tag = token.startswith('</')
is_processing_tag = token.startswith('<?')
is_cdata = token.startswith('<!')
@@ -239,11 +218,13 @@ class InvestmentAccount(Account):
super(InvestmentAccount, self).__init__()
self.brokerid = ''
+
class BrokerageBalance:
def __init__(self):
self.name = None
self.description = None
- self.value = None # decimal
+ self.value = None # decimal
+
class Security:
def __init__(self, uniqueid, name, ticker, memo):
@@ -293,7 +274,7 @@ class Signon:
ret += "\t\t\t</FI>\r\n"
if self.intu_bid is not None:
ret += "\t\t\t<INTU.BID>" + self.intu_bid + "\r\n"
- ret += "\t\t</SONRS>\r\n"
+ ret += "\t\t</SONRS>\r\n"
ret += "\t</SIGNONMSGSRSV1>\r\n"
return ret
@@ -354,6 +335,7 @@ class InvestmentTransaction(object):
self.commission = decimal.Decimal(0)
self.fees = decimal.Decimal(0)
self.total = decimal.Decimal(0)
+ self.tferaction = None
def __repr__(self):
return "<InvestmentTransaction type=" + str(self.type) + ", \
@@ -365,6 +347,7 @@ class Position(object):
self.security = ''
self.units = decimal.Decimal(0)
self.unit_price = decimal.Decimal(0)
+ self.market_value = decimal.Decimal(0)
class Institution(object):
@@ -379,7 +362,7 @@ class OfxParserException(Exception):
class OfxParser(object):
@classmethod
- def parse(cls_, file_handle, fail_fast=True):
+ def parse(cls, file_handle, fail_fast=True, custom_date_format=None):
'''
parse is the main entry point for an OfxParser. It takes a file
handle and an optional log_errors flag.
@@ -391,7 +374,8 @@ class OfxParser(object):
that statements will include bad transactions (which are marked).
'''
- cls_.fail_fast = fail_fast
+ cls.fail_fast = fail_fast
+ cls.custom_date_format = custom_date_format
if not hasattr(file_handle, 'seek'):
raise TypeError(six.u('parse() accepts a seek-able file handle\
@@ -405,14 +389,13 @@ class OfxParser(object):
ofx_obj.accounts = []
ofx_obj.signon = None
- skip_headers(ofx_file.fh)
ofx = soup_maker(ofx_file.fh)
if ofx.find('ofx') is None:
raise OfxParserException('The ofx file is empty!')
sonrs_ofx = ofx.find('sonrs')
if sonrs_ofx:
- ofx_obj.signon = cls_.parseSonrs(sonrs_ofx)
+ ofx_obj.signon = cls.parseSonrs(sonrs_ofx)
stmttrnrs = ofx.find('stmttrnrs')
if stmttrnrs:
@@ -429,32 +412,47 @@ class OfxParser(object):
ofx_obj.status['severity'] = \
stmttrnrs_status.find('severity').contents[0].strip()
+ ccstmttrnrs = ofx.find('ccstmttrnrs')
+ if ccstmttrnrs:
+ ccstmttrnrs_trnuid = ccstmttrnrs.find('trnuid')
+ if ccstmttrnrs_trnuid:
+ ofx_obj.trnuid = ccstmttrnrs_trnuid.contents[0].strip()
+
+ ccstmttrnrs_status = ccstmttrnrs.find('status')
+ if ccstmttrnrs_status:
+ ofx_obj.status = {}
+ ofx_obj.status['code'] = int(
+ ccstmttrnrs_status.find('code').contents[0].strip()
+ )
+ ofx_obj.status['severity'] = \
+ ccstmttrnrs_status.find('severity').contents[0].strip()
+
stmtrs_ofx = ofx.findAll('stmtrs')
if stmtrs_ofx:
- ofx_obj.accounts += cls_.parseStmtrs(stmtrs_ofx, AccountType.Bank)
+ ofx_obj.accounts += cls.parseStmtrs(stmtrs_ofx, AccountType.Bank)
ccstmtrs_ofx = ofx.findAll('ccstmtrs')
if ccstmtrs_ofx:
- ofx_obj.accounts += cls_.parseStmtrs(
+ ofx_obj.accounts += cls.parseStmtrs(
ccstmtrs_ofx, AccountType.CreditCard)
invstmtrs_ofx = ofx.findAll('invstmtrs')
if invstmtrs_ofx:
- ofx_obj.accounts += cls_.parseInvstmtrs(invstmtrs_ofx)
+ ofx_obj.accounts += cls.parseInvstmtrs(invstmtrs_ofx)
seclist_ofx = ofx.find('seclist')
if seclist_ofx:
- ofx_obj.security_list = cls_.parseSeclist(seclist_ofx)
+ ofx_obj.security_list = cls.parseSeclist(seclist_ofx)
else:
ofx_obj.security_list = None
acctinfors_ofx = ofx.find('acctinfors')
if acctinfors_ofx:
- ofx_obj.accounts += cls_.parseAcctinfors(acctinfors_ofx, ofx)
+ ofx_obj.accounts += cls.parseAcctinfors(acctinfors_ofx, ofx)
fi_ofx = ofx.find('fi')
if fi_ofx:
for account in ofx_obj.accounts:
- account.institution = cls_.parseOrg(fi_ofx)
+ account.institution = cls.parseOrg(fi_ofx)
if ofx_obj.accounts:
ofx_obj.account = ofx_obj.accounts[0]
@@ -462,12 +460,12 @@ class OfxParser(object):
return ofx_obj
@classmethod
- def parseOfxDateTime(cls_, ofxDateTime):
+ def parseOfxDateTime(cls, ofxDateTime):
# dateAsString looks something like 20101106160000.00[-5:EST]
# for 6 Nov 2010 4pm UTC-5 aka EST
# Some places (e.g. Newfoundland) have non-integer offsets.
- res = re.search("\[(?P<tz>[-+]?\d+\.?\d*)\:\w*\]$", ofxDateTime)
+ res = re.search(r"\[(?P<tz>[-+]?\d+\.?\d*)\:\w*\]$", ofxDateTime)
if res:
tz = float(res.group('tz'))
else:
@@ -475,42 +473,44 @@ class OfxParser(object):
timeZoneOffset = datetime.timedelta(hours=tz)
- res = re.search("^[0-9]*\.([0-9]{0,5})", ofxDateTime)
+ res = re.search(r"^[0-9]*\.([0-9]{0,5})", ofxDateTime)
if res:
msec = datetime.timedelta(seconds=float("0." + res.group(1)))
else:
msec = datetime.timedelta(seconds=0)
try:
- local_date = datetime.datetime.strptime(
- ofxDateTime[:14], '%Y%m%d%H%M%S'
- )
+ local_date = datetime.datetime.strptime(ofxDateTime[:14], '%Y%m%d%H%M%S')
return local_date - timeZoneOffset + msec
- except:
+ except ValueError:
if ofxDateTime[:8] == "00000000":
return None
- return datetime.datetime.strptime(
- ofxDateTime[:8], '%Y%m%d') - timeZoneOffset + msec
+ if not cls.custom_date_format:
+ return datetime.datetime.strptime(
+ ofxDateTime[:8], '%Y%m%d') - timeZoneOffset + msec
+ else:
+ return datetime.datetime.strptime(
+ ofxDateTime[:8], cls.custom_date_format) - timeZoneOffset + msec
@classmethod
- def parseAcctinfors(cls_, acctinfors_ofx, ofx):
+ def parseAcctinfors(cls, acctinfors_ofx, ofx):
all_accounts = []
for i in acctinfors_ofx.findAll('acctinfo'):
accounts = []
if i.find('invacctinfo'):
- accounts += cls_.parseInvstmtrs([i])
+ accounts += cls.parseInvstmtrs([i])
elif i.find('ccacctinfo'):
- accounts += cls_.parseStmtrs([i], AccountType.CreditCard)
+ accounts += cls.parseStmtrs([i], AccountType.CreditCard)
elif i.find('bankacctinfo'):
- accounts += cls_.parseStmtrs([i], AccountType.Bank)
+ accounts += cls.parseStmtrs([i], AccountType.Bank)
else:
continue
- fi_ofx = ofx.find('fi')
- if fi_ofx:
- for account in ofx_obj.accounts:
- account.institution = cls_.parseOrg(fi_ofx)
+ fi_ofx = ofx.find('fi')
+ if fi_ofx:
+ for account in all_accounts:
+ account.institution = cls.parseOrg(fi_ofx)
desc = i.find('desc')
if hasattr(desc, 'contents'):
@@ -520,40 +520,40 @@ class OfxParser(object):
return all_accounts
@classmethod
- def parseInvstmtrs(cls_, invstmtrs_list):
+ def parseInvstmtrs(cls, invstmtrs_list):
ret = []
for invstmtrs_ofx in invstmtrs_list:
account = InvestmentAccount()
acctid_tag = invstmtrs_ofx.find('acctid')
- if (hasattr(acctid_tag, 'contents')):
+ if hasattr(acctid_tag, 'contents'):
try:
account.account_id = acctid_tag.contents[0].strip()
except IndexError:
account.warnings.append(
six.u("Empty acctid tag for %s") % invstmtrs_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
brokerid_tag = invstmtrs_ofx.find('brokerid')
- if (hasattr(brokerid_tag, 'contents')):
+ if hasattr(brokerid_tag, 'contents'):
try:
account.brokerid = brokerid_tag.contents[0].strip()
except IndexError:
account.warnings.append(
six.u("Empty brokerid tag for %s") % invstmtrs_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
account.type = AccountType.Investment
- if (invstmtrs_ofx):
- account.statement = cls_.parseInvestmentStatement(
+ if invstmtrs_ofx:
+ account.statement = cls.parseInvestmentStatement(
invstmtrs_ofx)
ret.append(account)
return ret
@classmethod
- def parseSeclist(cls_, seclist_ofx):
+ def parseSeclist(cls, seclist_ofx):
securityList = []
for secinfo_ofx in seclist_ofx.findAll('secinfo'):
uniqueid_tag = secinfo_ofx.find('uniqueid')
@@ -579,102 +579,108 @@ class OfxParser(object):
return securityList
@classmethod
- def parseInvestmentPosition(cls_, ofx):
+ def parseInvestmentPosition(cls, ofx):
position = Position()
tag = ofx.find('uniqueid')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
position.security = tag.contents[0].strip()
tag = ofx.find('units')
- if (hasattr(tag, 'contents')):
- position.units = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ position.units = cls.toDecimal(tag)
tag = ofx.find('unitprice')
- if (hasattr(tag, 'contents')):
- position.unit_price = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ position.unit_price = cls.toDecimal(tag)
+ tag = ofx.find('mktval')
+ if hasattr(tag, 'contents'):
+ position.market_value = cls.toDecimal(tag)
tag = ofx.find('dtpriceasof')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
try:
- position.date = cls_.parseOfxDateTime(tag.contents[0].strip())
+ position.date = cls.parseOfxDateTime(tag.contents[0].strip())
except ValueError:
raise
return position
@classmethod
- def parseInvestmentTransaction(cls_, ofx):
+ def parseInvestmentTransaction(cls, ofx):
transaction = InvestmentTransaction(ofx.name)
tag = ofx.find('fitid')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
transaction.id = tag.contents[0].strip()
tag = ofx.find('memo')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
transaction.memo = tag.contents[0].strip()
tag = ofx.find('dttrade')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
try:
- transaction.tradeDate = cls_.parseOfxDateTime(
+ transaction.tradeDate = cls.parseOfxDateTime(
tag.contents[0].strip())
except ValueError:
raise
tag = ofx.find('dtsettle')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
try:
- transaction.settleDate = cls_.parseOfxDateTime(
+ transaction.settleDate = cls.parseOfxDateTime(
tag.contents[0].strip())
except ValueError:
raise
tag = ofx.find('uniqueid')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
transaction.security = tag.contents[0].strip()
tag = ofx.find('incometype')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
transaction.income_type = tag.contents[0].strip()
tag = ofx.find('units')
- if (hasattr(tag, 'contents')):
- transaction.units = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ transaction.units = cls.toDecimal(tag)
tag = ofx.find('unitprice')
- if (hasattr(tag, 'contents')):
- transaction.unit_price = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ transaction.unit_price = cls.toDecimal(tag)
tag = ofx.find('commission')
- if (hasattr(tag, 'contents')):
- transaction.commission = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ transaction.commission = cls.toDecimal(tag)
tag = ofx.find('fees')
- if (hasattr(tag, 'contents')):
- transaction.fees = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ transaction.fees = cls.toDecimal(tag)
tag = ofx.find('total')
- if (hasattr(tag, 'contents')):
- transaction.total = cls_.toDecimal(tag)
+ if hasattr(tag, 'contents'):
+ transaction.total = cls.toDecimal(tag)
tag = ofx.find('inv401ksource')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
transaction.inv401ksource = tag.contents[0].strip()
+ tag = ofx.find('tferaction')
+ if hasattr(tag, 'contents'):
+ transaction.tferaction = tag.contents[0].strip()
return transaction
@classmethod
- def parseInvestmentStatement(cls_, invstmtrs_ofx):
+ def parseInvestmentStatement(cls, invstmtrs_ofx):
statement = InvestmentStatement()
currency_tag = invstmtrs_ofx.find('curdef')
if hasattr(currency_tag, "contents"):
statement.currency = currency_tag.contents[0].strip().lower()
invtranlist_ofx = invstmtrs_ofx.find('invtranlist')
- if (invtranlist_ofx is not None):
+ if invtranlist_ofx is not None:
tag = invtranlist_ofx.find('dtstart')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
try:
- statement.start_date = cls_.parseOfxDateTime(
+ statement.start_date = cls.parseOfxDateTime(
tag.contents[0].strip())
except IndexError:
statement.warnings.append(six.u('Empty start date.'))
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
except ValueError:
e = sys.exc_info()[1]
statement.warnings.append(six.u('Invalid start date:\
%s') % e)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
tag = invtranlist_ofx.find('dtend')
- if (hasattr(tag, 'contents')):
+ if hasattr(tag, 'contents'):
try:
- statement.end_date = cls_.parseOfxDateTime(
+ statement.end_date = cls.parseOfxDateTime(
tag.contents[0].strip())
except IndexError:
statement.warnings.append(six.u('Empty end date.'))
@@ -682,18 +688,19 @@ class OfxParser(object):
e = sys.exc_info()[1]
statement.warnings.append(six.u('Invalid end date: \
%s') % e)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
- for transaction_type in ['posmf', 'posstock', 'posopt']:
+ for transaction_type in ['posmf', 'posstock', 'posopt', 'posother',
+ 'posdebt']:
try:
for investment_ofx in invstmtrs_ofx.findAll(transaction_type):
statement.positions.append(
- cls_.parseInvestmentPosition(investment_ofx))
+ cls.parseInvestmentPosition(investment_ofx))
except (ValueError, IndexError, decimal.InvalidOperation,
TypeError):
e = sys.exc_info()[1]
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
statement.discarded_entries.append(
{six.u('error'): six.u("Error parsing positions: \
@@ -704,31 +711,43 @@ class OfxParser(object):
try:
for investment_ofx in invstmtrs_ofx.findAll(transaction_type):
statement.transactions.append(
- cls_.parseInvestmentTransaction(investment_ofx))
+ cls.parseInvestmentTransaction(investment_ofx))
except (ValueError, IndexError, decimal.InvalidOperation):
e = sys.exc_info()[1]
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
statement.discarded_entries.append(
{six.u('error'): transaction_type + ": " + str(e),
six.u('content'): investment_ofx}
)
+ for transaction_ofx in invstmtrs_ofx.findAll('invbanktran'):
+ for stmt_ofx in transaction_ofx.findAll('stmttrn'):
+ try:
+ statement.transactions.append(
+ cls.parseTransaction(stmt_ofx))
+ except OfxParserException:
+ ofxError = sys.exc_info()[1]
+ statement.discarded_entries.append(
+ {'error': str(ofxError), 'content': transaction_ofx})
+ if cls.fail_fast:
+ raise
+
invbal_ofx = invstmtrs_ofx.find('invbal')
if invbal_ofx is not None:
- #<AVAILCASH>18073.98<MARGINBALANCE>+00000000000.00<SHORTBALANCE>+00000000000.00<BUYPOWER>+00000000000.00
+ # <AVAILCASH>18073.98<MARGINBALANCE>+00000000000.00<SHORTBALANCE>+00000000000.00<BUYPOWER>+00000000000.00
availcash_ofx = invbal_ofx.find('availcash')
if availcash_ofx is not None:
- statement.available_cash = cls_.toDecimal(availcash_ofx)
+ statement.available_cash = cls.toDecimal(availcash_ofx)
margin_balance_ofx = invbal_ofx.find('marginbalance')
if margin_balance_ofx is not None:
- statement.margin_balance = cls_.toDecimal(margin_balance_ofx)
+ statement.margin_balance = cls.toDecimal(margin_balance_ofx)
short_balance_ofx = invbal_ofx.find('shortbalance')
if short_balance_ofx is not None:
- statement.short_balance = cls_.toDecimal(short_balance_ofx)
+ statement.short_balance = cls.toDecimal(short_balance_ofx)
buy_power_ofx = invbal_ofx.find('buypower')
if buy_power_ofx is not None:
- statement.buy_power = cls_.toDecimal(buy_power_ofx)
+ statement.buy_power = cls.toDecimal(buy_power_ofx)
ballist_ofx = invbal_ofx.find('ballist')
if ballist_ofx is not None:
@@ -740,16 +759,17 @@ class OfxParser(object):
brokerage_balance.name = name_ofx.contents[0].strip()
description_ofx = balance_ofx.find('desc')
if description_ofx is not None:
- brokerage_balance.description = description_ofx.contents[0].strip()
+ brokerage_balance.description = \
+ description_ofx.contents[0].strip()
value_ofx = balance_ofx.find('value')
if value_ofx is not None:
- brokerage_balance.value = cls_.toDecimal(value_ofx)
+ brokerage_balance.value = cls.toDecimal(value_ofx)
statement.balance_list.append(brokerage_balance)
return statement
@classmethod
- def parseOrg(cls_, fi_ofx):
+ def parseOrg(cls, fi_ofx):
institution = Institution()
org = fi_ofx.find('org')
if hasattr(org, 'contents'):
@@ -762,7 +782,7 @@ class OfxParser(object):
return institution
@classmethod
- def parseSonrs(cls_, sonrs):
+ def parseSonrs(cls, sonrs):
items = [
'code',
@@ -779,7 +799,7 @@ class OfxParser(object):
for i in items:
try:
idict[i] = sonrs.find(i).contents[0].strip()
- except:
+ except Exception:
idict[i] = None
idict['code'] = int(idict['code'])
if idict['message'] is None:
@@ -788,35 +808,35 @@ class OfxParser(object):
return Signon(idict)
@classmethod
- def parseStmtrs(cls_, stmtrs_list, accountType):
+ def parseStmtrs(cls, stmtrs_list, accountType):
''' Parse the <STMTRS> tags and return a list of Accounts object. '''
ret = []
for stmtrs_ofx in stmtrs_list:
account = Account()
act_curdef = stmtrs_ofx.find('curdef')
- if act_curdef:
+ if act_curdef and act_curdef.contents:
account.curdef = act_curdef.contents[0].strip()
acctid_tag = stmtrs_ofx.find('acctid')
- if hasattr(acctid_tag, 'contents'):
+ if acctid_tag and acctid_tag.contents:
account.account_id = acctid_tag.contents[0].strip()
bankid_tag = stmtrs_ofx.find('bankid')
- if hasattr(bankid_tag, 'contents'):
+ if bankid_tag and bankid_tag.contents:
account.routing_number = bankid_tag.contents[0].strip()
branchid_tag = stmtrs_ofx.find('branchid')
- if hasattr(branchid_tag, 'contents'):
+ if branchid_tag and branchid_tag.contents:
account.branch_id = branchid_tag.contents[0].strip()
type_tag = stmtrs_ofx.find('accttype')
- if hasattr(type_tag, 'contents'):
+ if type_tag and type_tag.contents:
account.account_type = type_tag.contents[0].strip()
account.type = accountType
if stmtrs_ofx:
- account.statement = cls_.parseStatement(stmtrs_ofx)
+ account.statement = cls.parseStatement(stmtrs_ofx)
ret.append(account)
return ret
@classmethod
- def parseBalance(cls_, statement, stmt_ofx, bal_tag_name, bal_attr,
+ def parseBalance(cls, statement, stmt_ofx, bal_tag_name, bal_attr,
bal_date_attr, bal_type_string):
bal_tag = stmt_ofx.find(bal_tag_name)
if hasattr(bal_tag, "contents"):
@@ -824,34 +844,33 @@ class OfxParser(object):
dtasof_tag = bal_tag.find('dtasof')
if hasattr(balamt_tag, "contents"):
try:
- setattr(statement, bal_attr, cls_.toDecimal(balamt_tag))
+ setattr(statement, bal_attr, cls.toDecimal(balamt_tag))
except (IndexError, decimal.InvalidOperation):
- ex = sys.exc_info()[1]
statement.warnings.append(
six.u("%s balance amount was empty for \
%s") % (bal_type_string, stmt_ofx))
- if cls_.fail_fast:
+ if cls.fail_fast:
raise OfxParserException("Empty %s balance\
" % bal_type_string)
if hasattr(dtasof_tag, "contents"):
try:
- setattr(statement, bal_date_attr, cls_.parseOfxDateTime(
+ setattr(statement, bal_date_attr, cls.parseOfxDateTime(
dtasof_tag.contents[0].strip()))
except IndexError:
statement.warnings.append(
six.u("%s balance date was empty for %s\
") % (bal_type_string, stmt_ofx))
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
except ValueError:
statement.warnings.append(
six.u("%s balance date was not allowed for \
%s") % (bal_type_string, stmt_ofx))
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
@classmethod
- def parseStatement(cls_, stmt_ofx):
+ def parseStatement(cls, stmt_ofx):
'''
Parse a statement in ofx-land and return a Statement object.
'''
@@ -859,42 +878,41 @@ class OfxParser(object):
dtstart_tag = stmt_ofx.find('dtstart')
if hasattr(dtstart_tag, "contents"):
try:
- statement.start_date = cls_.parseOfxDateTime(
+ statement.start_date = cls.parseOfxDateTime(
dtstart_tag.contents[0].strip())
except IndexError:
statement.warnings.append(
six.u("Statement start date was empty for %s") % stmt_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
except ValueError:
statement.warnings.append(
six.u("Statement start date was not allowed for \
%s") % stmt_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
dtend_tag = stmt_ofx.find('dtend')
if hasattr(dtend_tag, "contents"):
try:
- statement.end_date = cls_.parseOfxDateTime(
+ statement.end_date = cls.parseOfxDateTime(
dtend_tag.contents[0].strip())
except IndexError:
statement.warnings.append(
six.u("Statement start date was empty for %s") % stmt_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
except ValueError:
- ve = sys.exc_info()[1]
msg = six.u("Statement start date was not formatted "
"correctly for %s")
statement.warnings.append(msg % stmt_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
except TypeError:
statement.warnings.append(
six.u("Statement start date was not allowed for \
%s") % stmt_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
currency_tag = stmt_ofx.find('curdef')
@@ -904,30 +922,30 @@ class OfxParser(object):
except IndexError:
statement.warnings.append(
six.u("Currency definition was empty for %s") % stmt_ofx)
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
- cls_.parseBalance(statement, stmt_ofx, 'ledgerbal',
- 'balance', 'balance_date', 'ledger')
+ cls.parseBalance(statement, stmt_ofx, 'ledgerbal',
+ 'balance', 'balance_date', 'ledger')
- cls_.parseBalance(statement, stmt_ofx, 'availbal', 'available_balance',
- 'available_balance_date', 'ledger')
+ cls.parseBalance(statement, stmt_ofx, 'availbal', 'available_balance',
+ 'available_balance_date', 'ledger')
for transaction_ofx in stmt_ofx.findAll('stmttrn'):
try:
statement.transactions.append(
- cls_.parseTransaction(transaction_ofx))
+ cls.parseTransaction(transaction_ofx))
except OfxParserException:
ofxError = sys.exc_info()[1]
statement.discarded_entries.append(
{'error': str(ofxError), 'content': transaction_ofx})
- if cls_.fail_fast:
+ if cls.fail_fast:
raise
return statement
@classmethod
- def parseTransaction(cls_, txn_ofx):
+ def parseTransaction(cls, txn_ofx):
'''
Parse a transaction in ofx-land and return a Transaction object.
'''
@@ -966,7 +984,7 @@ class OfxParser(object):
amt_tag = txn_ofx.find('trnamt')
if hasattr(amt_tag, "contents"):
try:
- transaction.amount = cls_.toDecimal(amt_tag)
+ transaction.amount = cls.toDecimal(amt_tag)
except IndexError:
raise OfxParserException("Invalid Transaction Date")
except decimal.InvalidOperation:
@@ -987,7 +1005,7 @@ class OfxParser(object):
date_tag = txn_ofx.find('dtposted')
if hasattr(date_tag, "contents"):
try:
- transaction.date = cls_.parseOfxDateTime(
+ transaction.date = cls.parseOfxDateTime(
date_tag.contents[0].strip())
except IndexError:
raise OfxParserException("Invalid Transaction Date")
@@ -1030,7 +1048,7 @@ class OfxParser(object):
raise OfxParserException(six.u("Empty transaction Merchant Category \
Code (MCC)"))
except AttributeError:
- if cls._fail_fast:
+ if cls.fail_fast:
raise
checknum_tag = txn_ofx.find('checknum')
@@ -1044,8 +1062,15 @@ class OfxParser(object):
return transaction
@classmethod
- def toDecimal(cls_, tag):
+ def toDecimal(cls, tag):
d = tag.contents[0].strip()
+ # Handle 10,000.50 formatted numbers
+ if re.search(r'.*\..*,', d):
+ d = d.replace('.', '')
+ # Handle 10.000,50 formatted numbers
+ if re.search(r'.*,.*\.', d):
+ d = d.replace(',', '')
+ # Handle 10000,50 formatted numbers
if '.' not in d and ',' in d:
d = d.replace(',', '.')
return decimal.Decimal(d)