ofx_parser 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,128 @@
1
+ module OfxParser
2
+ # This class is returned when a parse is successful.
3
+ # == General Notes
4
+ # * currency symbols are an iso4217 3-letter code
5
+ # * language is defined by iso639 3-letter code
6
+ class Ofx
7
+ attr_accessor :header, :sign_on, :signup_account_info,
8
+ :bank_account, :credit_card, :investment
9
+
10
+ def accounts
11
+ accounts = []
12
+ [:bank_account, :credit_card, :investment].each do |method|
13
+ val = send(method)
14
+ accounts << val if val
15
+ end
16
+ accounts
17
+ end
18
+ end
19
+
20
+ class SignOn
21
+ attr_accessor :status, :date, :language, :institute
22
+ end
23
+
24
+ class AccountInfo
25
+ attr_accessor :desc, :number
26
+ end
27
+
28
+ class Account
29
+ attr_accessor :number, :statement, :transaction_uid, :routing_number
30
+ end
31
+
32
+ class BankAccount < Account
33
+ TYPE = [:CHECKING, :SAVINGS, :MONEYMRKT, :CREDITLINE]
34
+ attr_accessor :type, :balance, :balance_date
35
+
36
+ undef type
37
+ def type
38
+ @type.to_s.upcase.to_sym
39
+ end
40
+ end
41
+
42
+ class CreditAccount < Account
43
+ attr_accessor :remaining_credit, :remaining_credit_date, :balance, :balance_date
44
+ end
45
+
46
+ class InvestmentAccount < Account
47
+ attr_accessor :broker_id, :positions, :margin_balance, :short_balance, :cash_balance
48
+ end
49
+
50
+
51
+ class Statement
52
+ attr_accessor :currency, :transactions, :start_date, :end_date
53
+ end
54
+
55
+ class Transaction
56
+ attr_accessor :type, :date, :amount, :fit_id, :check_number, :sic, :memo, :payee
57
+
58
+ TYPE = {
59
+ :CREDIT => "Generic credit",
60
+ :DEBIT => "Generic debit",
61
+ :INT => "Interest earned or paid ",
62
+ :DIV => "Dividend",
63
+ :FEE => "FI fee",
64
+ :SRVCHG => "Service charge",
65
+ :DEP => "Deposit",
66
+ :ATM => "ATM debit or credit",
67
+ :POS => "Point of sale debit or credit ",
68
+ :XFER => "Transfer",
69
+ :CHECK => "Check",
70
+ :PAYMENT => "Electronic payment",
71
+ :CASH => "Cash withdrawal",
72
+ :DIRECTDEP => "Direct deposit",
73
+ :DIRECTDEBIT => "Merchant initiated debit",
74
+ :REPEATPMT => "Repeating payment/standing order",
75
+ :OTHER => "Other"
76
+ }
77
+
78
+ def type_desc
79
+ TYPE[type]
80
+ end
81
+
82
+ undef type
83
+ def type
84
+ @type.to_s.strip.upcase.to_sym
85
+ end
86
+
87
+ undef sic
88
+ def sic
89
+ @sic == "" ? nil : @sic
90
+ end
91
+
92
+ def sic_desc
93
+ Mcc::CODES[sic]
94
+ end
95
+ end
96
+
97
+ class Position
98
+ end
99
+
100
+ # Status of a sign on
101
+ class Status
102
+ attr_accessor :code, :severity, :message
103
+
104
+ CODES = {
105
+ '0' => 'Success',
106
+ '2000' => 'General error',
107
+ '15000' => 'Must change USERPASS',
108
+ '15500' => 'Signon invalid',
109
+ '15501' => 'Customer account already in use',
110
+ '15502' => 'USERPASS Lockout'
111
+ }
112
+
113
+ def code_desc
114
+ CODES[code]
115
+ end
116
+
117
+ undef code
118
+ def code
119
+ @code.to_s.strip
120
+ end
121
+
122
+ end
123
+
124
+ class Institute
125
+ attr_accessor :name, :id
126
+ end
127
+
128
+ end
@@ -0,0 +1,188 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'time'
4
+ require 'date'
5
+
6
+ module OfxParser
7
+ VERSION = '1.0.6'
8
+
9
+ class OfxParser
10
+
11
+ # Creates and returns an Ofx instance when given a well-formed OFX document,
12
+ # complete with the mandatory key:pair header.
13
+ def self.parse(ofx)
14
+ ofx = ofx.respond_to?(:read) ? ofx.read.to_s : ofx.to_s
15
+
16
+ return Ofx.new if ofx == ""
17
+
18
+ header, body = pre_process(ofx)
19
+
20
+ ofx_out = parse_body(body)
21
+ ofx_out.header = header
22
+ ofx_out
23
+ end
24
+
25
+ # Designed to make the main OFX body parsable. This means adding closing tags
26
+ # to the SGML to make it parsable by hpricot.
27
+ #
28
+ # Returns an array of 2 elements:
29
+ # * header as a hash,
30
+ # * body as an evily pre-processed string ready for parsing by hpricot.
31
+ def self.pre_process(ofx)
32
+ header, body = ofx.split(/\n{2,}|:?<OFX>/, 2)
33
+
34
+ header = Hash[*header.gsub(/^\r?\n+/,'').split(/\r\n/).collect do |e|
35
+ e.split(/:/,2)
36
+ end.flatten]
37
+
38
+ body.gsub!(/>\s+</m, '><')
39
+ body.gsub!(/\s+</m, '<')
40
+ body.gsub!(/>\s+/m, '>')
41
+ body.gsub!(/<(\w+?)>([^<]+)/m, '<\1>\2</\1>')
42
+
43
+ [header, body]
44
+ end
45
+
46
+ # Takes an OFX datetime string of the format:
47
+ # * YYYYMMDDHHMMSS.XXX[gmt offset:tz name]
48
+ # * YYYYMMDD
49
+ # * YYYYMMDDHHMMSS
50
+ # * YYYYMMDDHHMMSS.XXX
51
+ #
52
+ # Returns a DateTime object. Milliseconds (XXX) are ignored.
53
+ def self.parse_datetime(date)
54
+ if /\A\s*
55
+ (\d{4})(\d{2})(\d{2}) ?# YYYYMMDD 1,2,3
56
+ (?:(\d{2})(\d{2})(\d{2}))? ?# HHMMSS - optional 4,5,6
57
+ (?:\.(\d{3}))? ?# .XXX - optional 7
58
+ (?:\[([-+]?[\.\d]+)\:\w{3}\])? ?# [-n:TZ] - optional 8,9
59
+ \s*\z/ix =~ date
60
+ year = $1.to_i
61
+ mon = $2.to_i
62
+ day = $3.to_i
63
+ hour = $4.to_i
64
+ min = $5.to_i
65
+ sec = $6.to_i
66
+ # DateTime does not support usecs.
67
+ # usec = 0
68
+ # usec = $7.to_f * 1000000 if $7
69
+ off = Rational($8.to_i, 24) # offset as a fraction of day. :|
70
+ DateTime.civil(year, mon, day, hour, min, sec, off)
71
+ end
72
+ end
73
+
74
+ private
75
+ def self.parse_body(body)
76
+ doc = Hpricot.XML(body)
77
+
78
+ ofx = Ofx.new
79
+
80
+ ofx.sign_on = build_signon((doc/"SIGNONMSGSRSV1/SONRS"))
81
+ ofx.signup_account_info = build_info((doc/"SIGNUPMSGSRSV1/ACCTINFOTRNRS"))
82
+ ofx.bank_account = build_bank((doc/"BANKMSGSRSV1/STMTTRNRS")) unless (doc/"BANKMSGSRSV1").empty?
83
+ ofx.credit_card = build_credit((doc/"CREDITCARDMSGSRSV1/CCSTMTTRNRS")) unless (doc/"CREDITCARDMSGSRSV1").empty?
84
+ #build_investment((doc/"SIGNONMSGSRQV1"))
85
+
86
+ ofx
87
+ end
88
+
89
+ def self.build_signon(doc)
90
+ sign_on = SignOn.new
91
+ sign_on.status = build_status((doc/"STATUS"))
92
+ sign_on.date = parse_datetime((doc/"DTSERVER").inner_text)
93
+ sign_on.language = (doc/"LANGUAGE").inner_text
94
+
95
+ sign_on.institute = Institute.new
96
+ sign_on.institute.name = ((doc/"FI")/"ORG").inner_text
97
+ sign_on.institute.id = ((doc/"FI")/"FID").inner_text
98
+ sign_on
99
+ end
100
+
101
+ def self.build_info(doc)
102
+ account_infos = []
103
+
104
+ (doc/"ACCTINFO").each do |info_doc|
105
+ acc_info = AccountInfo.new
106
+ acc_info.desc = (info_doc/"DESC").inner_text
107
+ acc_info.number = (info_doc/"ACCTID").inner_text
108
+ account_infos << acc_info
109
+ end
110
+
111
+ account_infos
112
+ end
113
+
114
+ def self.build_bank(doc)
115
+ acct = BankAccount.new
116
+
117
+ acct.transaction_uid = (doc/"TRNUID").inner_text.strip
118
+ acct.number = (doc/"STMTRS/BANKACCTFROM/ACCTID").inner_text
119
+ acct.routing_number = (doc/"STMTRS/BANKACCTFROM/BANKID").inner_text
120
+ acct.type = (doc/"STMTRS/BANKACCTFROM/ACCTTYPE").inner_text.strip
121
+ acct.balance = (doc/"STMTRS/LEDGERBAL/BALAMT").inner_text
122
+ acct.balance_date = parse_datetime((doc/"STMTRS/LEDGERBAL/DTASOF").inner_text)
123
+
124
+ statement = Statement.new
125
+ statement.currency = (doc/"STMTRS/CURDEF").inner_text
126
+ statement.start_date = parse_datetime((doc/"STMTRS/BANKTRANLIST/DTSTART").inner_text)
127
+ statement.end_date = parse_datetime((doc/"STMTRS/BANKTRANLIST/DTEND").inner_text)
128
+ acct.statement = statement
129
+
130
+ statement.transactions = (doc/"STMTRS/BANKTRANLIST/STMTTRN").collect do |t|
131
+ build_transaction(t)
132
+ end
133
+
134
+ acct
135
+ end
136
+
137
+ def self.build_credit(doc)
138
+ acct = CreditAccount.new
139
+
140
+ acct.number = (doc/"CCSTMTRS/CCACCTFROM/ACCTID").inner_text
141
+ acct.transaction_uid = (doc/"TRNUID").inner_text.strip
142
+ acct.balance = (doc/"CCSTMTRS/LEDGERBAL/BALAMT").inner_text
143
+ acct.balance_date = parse_datetime((doc/"CCSTMTRS/LEDGERBAL/DTASOF").inner_text)
144
+ acct.remaining_credit = (doc/"CCSTMTRS/AVAILBAL/BALAMT").inner_text
145
+ acct.remaining_credit_date = parse_datetime((doc/"CCSTMTRS/AVAILBAL/DTASOF").inner_text)
146
+
147
+ statement = Statement.new
148
+ statement.currency = (doc/"CCSTMTRS/CURDEF").inner_text
149
+ statement.start_date = parse_datetime((doc/"CCSTMTRS/BANKTRANLIST/DTSTART").inner_text)
150
+ statement.end_date = parse_datetime((doc/"CCSTMTRS/BANKTRANLIST/DTEND").inner_text)
151
+ acct.statement = statement
152
+
153
+ statement.transactions = (doc/"CCSTMTRS/BANKTRANLIST/STMTTRN").collect do |t|
154
+ build_transaction(t)
155
+ end
156
+
157
+ acct
158
+ end
159
+
160
+ # for credit and bank transactions.
161
+ def self.build_transaction(t)
162
+ transaction = Transaction.new
163
+ transaction.type = (t/"TRNTYPE").inner_text
164
+ transaction.date = parse_datetime((t/"DTPOSTED").inner_text)
165
+ transaction.amount = (t/"TRNAMT").inner_text
166
+ transaction.fit_id = (t/"FITID").inner_text
167
+ transaction.payee = (t/"PAYEE").inner_text + (t/"NAME").inner_text
168
+ transaction.memo = (t/"MEMO").inner_text
169
+ transaction.sic = (t/"SIC").inner_text
170
+ transaction.check_number = (t/"CHECKNUM").inner_text if transaction.type == :CHECK
171
+ transaction
172
+ end
173
+
174
+
175
+ def self.build_investment(doc)
176
+
177
+ end
178
+
179
+ def self.build_status(doc)
180
+ status = Status.new
181
+ status.code = (doc/"CODE").inner_text
182
+ status.severity = (doc/"SEVERITY").inner_text
183
+ status.message = (doc/"MESSAGE").inner_text
184
+ status
185
+ end
186
+
187
+ end
188
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ofx_parser"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ofx_parser"
7
+ s.version = OfxParser::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Andrew A. Smith", "Armand du Plessis"]
10
+ s.email = ["adp@bank.io"]
11
+ s.homepage = "http://ofx-parser.rubyforge.org/"
12
+ s.summary = %q{ofx-parser is an OFX 1.x parser written in Ruby. Forked from original ofx-parser gem to remove UK monetary support}
13
+ s.description = %q{ofx-parser is an OFX 1.x parser written in Ruby. Forked from original ofx-parser gem to remove UK monetary support}
14
+
15
+ s.rubyforge_project = "ofx_parser"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_runtime_dependency 'hpricot'
23
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,87 @@
1
+ OFXHEADER:100
2
+ DATA:OFXSGML
3
+ VERSION:102
4
+ SECURITY:NONE
5
+ ENCODING:USASCII
6
+ CHARSET:1252
7
+ COMPRESSION:NONE
8
+ OLDFILEUID:
9
+ NEWFILEUID:NONE
10
+
11
+ <OFX>
12
+ <SIGNONMSGSRSV1>
13
+ <SONRS>
14
+ <STATUS>
15
+ <CODE>0</CODE>
16
+ <SEVERITY>INFO</SEVERITY>
17
+ <MESSAGE>The user is authentic; operation succeeded.</MESSAGE>
18
+ </STATUS>
19
+ <DTSERVER>20070623142635.169[-5:CDT]</DTSERVER>
20
+ <LANGUAGE>ENG</LANGUAGE>
21
+ <FI>
22
+ <ORG>U.S. Bank</ORG>
23
+ <FID>1402</FID>
24
+ </FI>
25
+ <INTU.BID>1402</INTU.BID>
26
+ </SONRS>
27
+ </SIGNONMSGSRSV1>
28
+ <BANKMSGSRSV1>
29
+ <STMTTRNRS>
30
+ <TRNUID>9C24229A0077EAA50000011353C9E00743FC</TRNUID>
31
+ <STATUS>
32
+ <CODE>0</CODE>
33
+ <SEVERITY>INFO</SEVERITY>
34
+ </STATUS>
35
+ <STMTRS>
36
+ <CURDEF>USD</CURDEF>
37
+ <BANKACCTFROM>
38
+ <BANKID>033000033</BANKID>
39
+ <ACCTID>103333333333</ACCTID>
40
+ <ACCTTYPE>CHECKING</ACCTTYPE>
41
+ </BANKACCTFROM>
42
+ <BANKTRANLIST>
43
+ <DTSTART>20070604190000.000[-5:CDT]</DTSTART>
44
+ <DTEND>20070622190000.000[-5:CDT]</DTEND>
45
+ <STMTTRN>
46
+ <TRNTYPE>PAYMENT</TRNTYPE>
47
+ <DTPOSTED>20070606120000.000</DTPOSTED>
48
+ <TRNAMT>-11.11</TRNAMT>
49
+ <FITID>11111111 22</FITID>
50
+ <NAME>WEB AUTHORIZED PMT FOO INC</NAME>
51
+ <MEMO>Download from usbank.com. FOO INC</MEMO>
52
+ </STMTTRN>
53
+ <STMTTRN>
54
+ <TRNTYPE>CHECK</TRNTYPE>
55
+ <DTPOSTED>20070607120000.000</DTPOSTED>
56
+ <TRNAMT>-111.11</TRNAMT>
57
+ <FITID>22222A</FITID>
58
+ <CHECKNUM>0000009611</CHECKNUM>
59
+ <NAME>CHECK</NAME>
60
+ <MEMO>Download from usbank.com.</MEMO>
61
+ </STMTTRN>
62
+ <STMTTRN>
63
+ <TRNTYPE>DIRECTDEP</TRNTYPE>
64
+ <DTPOSTED>20070614120000.000</DTPOSTED>
65
+ <TRNAMT>1111.11</TRNAMT>
66
+ <FITID>X34AE33</FITID>
67
+ <NAME>ELECTRONIC DEPOSIT BAR INC</NAME>
68
+ <MEMO>Download from usbank.com. BAR INC</MEMO>
69
+ </STMTTRN>
70
+ <STMTTRN>
71
+ <TRNTYPE>CREDIT</TRNTYPE>
72
+ <DTPOSTED>20070619120000.000</DTPOSTED>
73
+ <TRNAMT>11.11</TRNAMT>
74
+ <FITID>8 8 9089743</FITID>
75
+ <NAME>ATM DEPOSIT US BANK ANYTOWNAS</NAME>
76
+ <MEMO>Download from usbank.com. US BANK ANYTOWN ASUS1</MEMO>
77
+ </STMTTRN>
78
+ </BANKTRANLIST>
79
+ <LEDGERBAL>
80
+ <BALAMT>1234.09</BALAMT>
81
+ <DTASOF>20070623142635.369[-5:CDT]</DTASOF>
82
+ </LEDGERBAL>
83
+ </STMTRS>
84
+ </STMTTRNRS>
85
+ </BANKMSGSRSV1>
86
+ </OFX>
87
+
@@ -0,0 +1,79 @@
1
+ OFXHEADER:100
2
+ DATA:OFXSGML
3
+ VERSION:102
4
+ SECURITY:NONE
5
+ ENCODING:USASCII
6
+ CHARSET:1252
7
+ COMPRESSION:NONE
8
+ OLDFILEUID:NONE
9
+ NEWFILEUID:NONE
10
+
11
+ <OFX>
12
+ <SIGNONMSGSRSV1>
13
+ <SONRS>
14
+ <STATUS>
15
+ <CODE>0</CODE>
16
+ <SEVERITY>INFO</SEVERITY>
17
+ </STATUS>
18
+ <DTSERVER>20070623192010</DTSERVER>
19
+ <LANGUAGE>ENG</LANGUAGE>
20
+ <FI>
21
+ <ORG>Citigroup</ORG>
22
+ <FID>24909</FID>
23
+ </FI>
24
+ <INTU.BID>24909</INTU.BID>
25
+ </SONRS>
26
+ </SIGNONMSGSRSV1>
27
+ <CREDITCARDMSGSRSV1>
28
+ <CCSTMTTRNRS>
29
+ <TRNUID>0</TRNUID>
30
+ <STATUS>
31
+ <CODE>0</CODE>
32
+ <SEVERITY>INFO</SEVERITY>
33
+ </STATUS>
34
+ <CCSTMTRS>
35
+ <CURDEF>USD</CURDEF>
36
+ <CCACCTFROM>
37
+ <ACCTID>XXXXXXXXXXXX1111</ACCTID>
38
+ </CCACCTFROM>
39
+ <BANKTRANLIST>
40
+ <DTSTART>20070509120000</DTSTART>
41
+ <DTEND>20070608120000</DTEND>
42
+ <STMTTRN>
43
+ <TRNTYPE>DEBIT</TRNTYPE>
44
+ <DTPOSTED>20070510170000</DTPOSTED>
45
+ <TRNAMT>-19.17</TRNAMT>
46
+ <FITID>xx</FITID>
47
+ <SIC>5912</SIC>
48
+ <NAME>WALGREEN 34638675 ANYTOWN</NAME>
49
+ </STMTTRN>
50
+ <STMTTRN>
51
+ <TRNTYPE>DEBIT</TRNTYPE>
52
+ <DTPOSTED>20070512170000</DTPOSTED>
53
+ <TRNAMT>-12.0</TRNAMT>
54
+ <FITID>yy-56</FITID>
55
+ <SIC>7933</SIC>
56
+ <NAME>SUNSET BOWL ANYTOWN</NAME>
57
+ </STMTTRN>
58
+ <STMTTRN>
59
+ <TRNTYPE>CREDIT</TRNTYPE>
60
+ <DTPOSTED>20070526170000</DTPOSTED>
61
+ <TRNAMT>11.01</TRNAMT>
62
+ <FITID>78-9</FITID>
63
+ <SIC>0000</SIC>
64
+ <NAME>ELECTRONIC PAYMENT-THANK YOU</NAME>
65
+ </STMTTRN>
66
+ </BANKTRANLIST>
67
+ <LEDGERBAL>
68
+ <BALAMT>-1111.01</BALAMT>
69
+ <DTASOF>20070623192013</DTASOF>
70
+ </LEDGERBAL>
71
+ <AVAILBAL>
72
+ <BALAMT>19000.99</BALAMT>
73
+ <DTASOF>20070623192013</DTASOF>
74
+ </AVAILBAL>
75
+ </CCSTMTRS>
76
+ </CCSTMTTRNRS>
77
+ </CREDITCARDMSGSRSV1>
78
+ </OFX>
79
+