ofx_for_ruby 0.1.1

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.
Files changed (74) hide show
  1. data/.externalToolBuilders/Rebuild parsers.launch +10 -0
  2. data/.gitignore +9 -0
  3. data/.loadpath +5 -0
  4. data/.project +27 -0
  5. data/COPYING +674 -0
  6. data/Gemfile +2 -0
  7. data/README +47 -0
  8. data/RELEASE_NOTES +45 -0
  9. data/Rakefile +63 -0
  10. data/lib/ofx.rb +60 -0
  11. data/lib/ofx/1.0.2/banking_message_set.rb +261 -0
  12. data/lib/ofx/1.0.2/credit_card_statement_message_set.rb +269 -0
  13. data/lib/ofx/1.0.2/document.rb +34 -0
  14. data/lib/ofx/1.0.2/email_message_set.rb +34 -0
  15. data/lib/ofx/1.0.2/financial_institution_profile_message_set.rb +155 -0
  16. data/lib/ofx/1.0.2/header.rb +48 -0
  17. data/lib/ofx/1.0.2/interbank_funds_transfer_message_set.rb +30 -0
  18. data/lib/ofx/1.0.2/investment_security_list_message_set.rb +30 -0
  19. data/lib/ofx/1.0.2/investment_statement_message_set.rb +30 -0
  20. data/lib/ofx/1.0.2/lexer.rb +135 -0
  21. data/lib/ofx/1.0.2/message_set.rb +165 -0
  22. data/lib/ofx/1.0.2/ofx_102.racc +90 -0
  23. data/lib/ofx/1.0.2/ofx_102.rex +65 -0
  24. data/lib/ofx/1.0.2/parser.rb +215 -0
  25. data/lib/ofx/1.0.2/payment_message_set.rb +30 -0
  26. data/lib/ofx/1.0.2/serializer.rb +114 -0
  27. data/lib/ofx/1.0.2/signon_message_set.rb +121 -0
  28. data/lib/ofx/1.0.2/signup_message_set.rb +186 -0
  29. data/lib/ofx/1.0.2/statements.rb +54 -0
  30. data/lib/ofx/1.0.2/status.rb +30 -0
  31. data/lib/ofx/1.0.2/wire_funds_transfer_message_set.rb +30 -0
  32. data/lib/ofx/banking_message_set.rb +125 -0
  33. data/lib/ofx/credit_card_statement_message_set.rb +104 -0
  34. data/lib/ofx/document.rb +30 -0
  35. data/lib/ofx/email_message_set.rb +42 -0
  36. data/lib/ofx/file_unique_identifier.rb +28 -0
  37. data/lib/ofx/financial_client.rb +76 -0
  38. data/lib/ofx/financial_institution.rb +83 -0
  39. data/lib/ofx/financial_institution_profile_message_set.rb +67 -0
  40. data/lib/ofx/header.rb +115 -0
  41. data/lib/ofx/http/cacert.pem +3509 -0
  42. data/lib/ofx/http/ofx_http_client.rb +84 -0
  43. data/lib/ofx/interbank_funds_transfer_message_set.rb +33 -0
  44. data/lib/ofx/investment_security_list_message_set.rb +33 -0
  45. data/lib/ofx/investment_statement_message_set.rb +33 -0
  46. data/lib/ofx/message_set.rb +78 -0
  47. data/lib/ofx/payment_message_set.rb +33 -0
  48. data/lib/ofx/serializer.rb +39 -0
  49. data/lib/ofx/signon_message_set.rb +141 -0
  50. data/lib/ofx/signup_message_set.rb +84 -0
  51. data/lib/ofx/statements.rb +49 -0
  52. data/lib/ofx/status.rb +100 -0
  53. data/lib/ofx/transaction_unique_identifier.rb +28 -0
  54. data/lib/ofx/version.rb +82 -0
  55. data/lib/ofx/wire_funds_transfer_message_set.rb +33 -0
  56. data/ofx_for_ruby.gemspec +25 -0
  57. data/planning/OFX Specification completion.ods +0 -0
  58. data/test/capital_one/capital_one_helper.rb +77 -0
  59. data/test/capital_one/fixtures/README +10 -0
  60. data/test/capital_one/fixtures/fipid-5599.xml +98 -0
  61. data/test/capital_one/test_banking_statement.rb +108 -0
  62. data/test/capital_one/test_financial_institution_profile.rb +295 -0
  63. data/test/capital_one/test_ofx_http_client.rb +72 -0
  64. data/test/capital_one/test_signup_account_information.rb +100 -0
  65. data/test/citi/citi_helper.rb +77 -0
  66. data/test/citi/fixtures/README +9 -0
  67. data/test/citi/fixtures/fipid-6642.xml +98 -0
  68. data/test/citi/test_credit_card_statement.rb +159 -0
  69. data/test/citi/test_financial_institution_profile.rb +231 -0
  70. data/test/citi/test_ofx_http_client.rb +71 -0
  71. data/test/citi/test_signup_account_information.rb +85 -0
  72. data/test/test_helper.rb +4 -0
  73. data/test/test_ofx_version.rb +141 -0
  74. metadata +210 -0
@@ -0,0 +1,48 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class Header
20
+ def to_ofx_102_s
21
+ headers = ""
22
+
23
+ headers += "OFXHEADER:" + self.header_version.to_compact_s + "\n"
24
+ headers += "DATA:" + self.content_type.to_s + "\n"
25
+ headers += "VERSION:" + self.document_version.to_compact_s + "\n"
26
+ headers += "SECURITY:" + self.security.to_s + "\n"
27
+ headers += "ENCODING:" + self.content_encoding.to_s + "\n"
28
+ headers += "CHARSET:" + self.content_character_set.to_s + "\n"
29
+ headers += "COMPRESSION:" + self.compression.to_s + "\n"
30
+ headers += "OLDFILEUID:" + self.previous_unique_identifier.to_s + "\n"
31
+ headers += "NEWFILEUID:" + self.unique_identifier.to_s + "\n"
32
+
33
+ headers
34
+ end
35
+
36
+ def self.from_ofx_102_s(header_string)
37
+ header = OFX::Header.new
38
+
39
+ header_pattern = /^(\w+)\:(.*)$/
40
+ header_string.split("\n").each do |this_header|
41
+ header_match = header_pattern.match(this_header)
42
+ header[header_match[1]] = header_match[2]
43
+ end
44
+
45
+ header
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class InterbankFundsTransferMessageSet < MessageSet
20
+ end
21
+
22
+ class InterbankFundsTransferMessageSetProfile < MessageSetProfile
23
+ def self.from_ofx_102_hash(message_set_description_hash)
24
+ profile = OFX::InterbankFundsTransferMessageSetProfile.new
25
+ profile.message_set_class = OFX::InterbankFundsTransferMessageSet
26
+ profile.from_ofx_102_hash(message_set_description_hash)
27
+ profile
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class InvestmentSecurityListMessageSet < MessageSet
20
+ end
21
+
22
+ class InvestmentSecurityListMessageSetProfile < MessageSetProfile
23
+ def self.from_ofx_102_hash(message_set_description_hash)
24
+ profile = OFX::InvestmentSecurityListMessageSetProfile.new
25
+ profile.message_set_class = OFX::InvestmentSecurityListMessageSet
26
+ profile.from_ofx_102_hash(message_set_description_hash)
27
+ profile
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class InvestmentStatementMessageSet < MessageSet
20
+ end
21
+
22
+ class InvestmentStatementMessageSetProfile < MessageSetProfile
23
+ def self.from_ofx_102_hash(message_set_description_hash)
24
+ profile = OFX::InvestmentStatementMessageSetProfile.new
25
+ profile.message_set_class = OFX::InvestmentStatementMessageSet
26
+ profile.from_ofx_102_hash(message_set_description_hash)
27
+ profile
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,135 @@
1
+ #--
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by rex 1.0.2
4
+ # from lexical definition file "ofx_102.rex".
5
+ #++
6
+
7
+ require 'racc/parser'
8
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
9
+ #
10
+ # This file is part of OFX for Ruby.
11
+ #
12
+ # OFX for Ruby is free software; you can redistribute it and/or modify
13
+ # it under the terms of the GNU General Public License as published by
14
+ # the Free Software Foundation; either version 3 of the License, or
15
+ # (at your option) any later version.
16
+ #
17
+ # OFX for Ruby is distributed in the hope that it will be useful,
18
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ # GNU General Public License for more details.
21
+ #
22
+ # You should have received a copy of the GNU General Public License
23
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+
25
+ module OFX
26
+ module OFX102
27
+ class Parser < Racc::Parser
28
+ require 'strscan'
29
+
30
+ class ScanError < StandardError ; end
31
+
32
+ attr_reader :lineno
33
+ attr_reader :filename
34
+
35
+ def scan_setup ; end
36
+
37
+ def action &block
38
+ yield
39
+ end
40
+
41
+ def scan_str( str )
42
+ scan_evaluate str
43
+ do_parse
44
+ end
45
+
46
+ def load_file( filename )
47
+ @filename = filename
48
+ open(filename, "r") do |f|
49
+ scan_evaluate f.read
50
+ end
51
+ end
52
+
53
+ def scan_file( filename )
54
+ load_file filename
55
+ do_parse
56
+ end
57
+
58
+ def next_token
59
+ @rex_tokens.shift
60
+ end
61
+
62
+ def scan_evaluate( str )
63
+ scan_setup
64
+ @rex_tokens = []
65
+ @lineno = 1
66
+ ss = StringScanner.new(str)
67
+ state = nil
68
+ until ss.eos?
69
+ text = ss.peek(1)
70
+ @lineno += 1 if text == "\n"
71
+ case state
72
+ when nil
73
+ case
74
+ when (text = ss.scan(/\<\//))
75
+ @rex_tokens.push action { state = :TAG; [:etag_in, text] }
76
+
77
+ when (text = ss.scan(/\</))
78
+ @rex_tokens.push action { state = :TAG; [:tag_in, text] }
79
+
80
+ when (text = ss.scan(/\s+(?=\S)/))
81
+ ;
82
+
83
+ when (text = ss.scan(/.*?(?=(\<|\<\/)+?)/))
84
+ @rex_tokens.push action { [:text, text] }
85
+
86
+ when (text = ss.scan(/.*\S(?=\s*$)/))
87
+ @rex_tokens.push action { [:text, text] }
88
+
89
+ when (text = ss.scan(/\s+(?=$)/))
90
+ ;
91
+
92
+ else
93
+ text = ss.string[ss.pos .. -1]
94
+ raise ScanError, "can not match: '" + text + "'"
95
+ end # if
96
+
97
+ when :TAG
98
+ case
99
+ when (text = ss.scan(/\>/))
100
+ @rex_tokens.push action { state = nil; [:tag_out, text] }
101
+
102
+ when (text = ss.scan(/[\w\-\.]+/))
103
+ @rex_tokens.push action { [:element, text] }
104
+
105
+ else
106
+ text = ss.string[ss.pos .. -1]
107
+ raise ScanError, "can not match: '" + text + "'"
108
+ end # if
109
+
110
+ else
111
+ raise ScanError, "undefined state: '" + state.to_s + "'"
112
+ end # case state
113
+ end # until ss
114
+ end # def scan_evaluate
115
+
116
+ end # class
117
+
118
+ end # module OFX102
119
+ end # module OFX
120
+
121
+ =begin
122
+ if __FILE__ == $0
123
+ exit if ARGV.size != 1
124
+ filename = ARGV.shift
125
+ rex = OFX::OFX102::Parser.new
126
+ begin
127
+ rex.load_file filename
128
+ while token = rex.next_token
129
+ p token
130
+ end
131
+ rescue
132
+ $stderr.printf "%s:%d:%s\n", rex.filename, rex.lineno, $!.message
133
+ end
134
+ end
135
+ =end
@@ -0,0 +1,165 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class MessageSet
20
+ def to_ofx_102_s
21
+ request_message = requests.length > 0
22
+ request_message = responses.length == 0
23
+
24
+ message = ''
25
+
26
+ if request_message
27
+ message += " <#{ofx_102_message_set_name}MSGSRQV#{version}>\n"
28
+ requests.each do |request|
29
+ message += request.to_ofx_102_s + "\n"
30
+ end
31
+ message += " </#{ofx_102_message_set_name}MSGSRQV#{version}>\n"
32
+ else
33
+ message += " <#{ofx_102_message_set_name}MSGSRSV#{version}>\n"
34
+ responses.each do |response|
35
+ message += response.to_ofx_102_s + "\n"
36
+ end
37
+ message += " </#{ofx_102_message_set_name}MSGSRSV#{version}>\n"
38
+ end
39
+
40
+ message
41
+ end
42
+
43
+ def ofx_102_message_set_name
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def self.from_ofx_102_message_set_hash(message_set_name, message_set_hash)
48
+ match = /^(\w+)MSGSR([QS])V(\d+)$/.match(message_set_name)
49
+ raise NotImplementedError unless match
50
+
51
+ message_set_class = MESSAGE_SET_NAMES_TO_CLASSES[$1]
52
+ raise NotImplementedError unless message_set_class
53
+
54
+ return message_set_class.from_ofx_102_hash(message_set_hash)
55
+ end
56
+
57
+ def request_or_response_from_ofx_102_tag_name(response_or_request_name)
58
+ raise NotImplementedError
59
+ end
60
+
61
+ private
62
+ MESSAGE_SET_NAMES_TO_CLASSES =
63
+ {
64
+ 'SIGNON' => SignonMessageSet,
65
+ 'SIGNUP' => SignupMessageSet,
66
+ 'PROF' => FinancialInstitutionProfileMessageSet,
67
+ 'BANK' => BankingMessageSet,
68
+ 'CREDITCARD' => CreditCardStatementMessageSet
69
+ }
70
+ end
71
+
72
+ class MessageSetProfile
73
+ def from_ofx_102_hash(message_set_description_hash)
74
+ message_set_core_hash = message_set_description_hash['MSGSETCORE']
75
+ @version = OFX::Version.new(message_set_core_hash['VER'])
76
+ @service_provider_name = message_set_core_hash['SPNAME']
77
+ @message_url = URI.parse(message_set_core_hash['URL']) if message_set_core_hash['URL']
78
+ @required_ofx_security = message_set_core_hash['OFXSEC']
79
+ @requires_transport_security = message_set_core_hash['TRANSPSEC'] == 'Y' ? true : false
80
+ @signon_realm = message_set_core_hash['SIGNONREALM']
81
+ @language = message_set_core_hash['LANGUAGE']
82
+ @synchronization_mode = message_set_core_hash['SYNCMODE']
83
+ @supports_response_file_error_recovery = message_set_core_hash['RESPFILEER'] == 'Y' ? true : false
84
+ end
85
+ end
86
+
87
+ class Request
88
+ def to_ofx_102_s
89
+ request = ''
90
+ request += " <#{ofx_102_name}RQ>\n"
91
+
92
+ request += ofx_102_request_body + "\n"
93
+
94
+ request += " </#{ofx_102_name}RQ>"
95
+ request
96
+ end
97
+
98
+ def ofx_102_request_body
99
+ raise NotImplementedError
100
+ end
101
+ end
102
+
103
+ class TransactionalRequest < Request
104
+ def to_ofx_102_s
105
+ request = ''
106
+ request += " <#{ofx_102_name}TRNRQ>\n"
107
+
108
+ request += " <TRNUID>#{transaction_identifier}\n"
109
+ request += " <CLTCOOKIE>#{client_cookie}\n" if client_cookie
110
+ request += " <TAN>#{transaction_authorization_number}\n" if transaction_authorization_number
111
+
112
+ request += " <#{ofx_102_name}RQ>\n"
113
+
114
+ request += ofx_102_request_body + "\n"
115
+
116
+ request += " </#{ofx_102_name}RQ>\n"
117
+
118
+ request += " </#{ofx_102_name}TRNRQ>"
119
+ request
120
+ end
121
+
122
+ def ofx_102_request_body
123
+ raise NotImplementedError
124
+ end
125
+ end
126
+
127
+ class Response
128
+ def to_ofx_102_s
129
+ response = ''
130
+ response += " <#{ofx_102_name}RS>\n"
131
+
132
+ response += ofx_102_response_body + "\n"
133
+
134
+ response += " </#{ofx_102_name}RS>"
135
+ response
136
+ end
137
+
138
+ def ofx_102_response_body
139
+ raise NotImplementedError
140
+ end
141
+ end
142
+
143
+ class TransactionalResponse < Response
144
+ def to_ofx_102_s
145
+ response = ''
146
+ response += " <#{ofx_102_name}TRNRS>\n"
147
+
148
+ response += " <TRNUID>#{transaction_identifier}\n"
149
+ response += " <CLTCOOKIE>#{client_cookie}\n" if client_cookie
150
+
151
+ response += " <#{ofx_102_name}RS>\n"
152
+
153
+ response += ofx_102_response_body + "\n"
154
+
155
+ response += " </#{ofx_102_name}RS>\n"
156
+
157
+ response += " </#{ofx_102_name}TRNRS>"
158
+ response
159
+ end
160
+
161
+ def ofx_102_response_body
162
+ raise NotImplementedError
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,90 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ class OFX::OFX102::Parser
19
+
20
+ rule
21
+ target :
22
+ | ofx_body
23
+
24
+ ofx_body : tag_from contents tag_to
25
+
26
+ tag_from : tag_in element tag_out { start_tag(val[1]) }
27
+ tag_to : etag_in element tag_out { end_tag(val[1]) }
28
+
29
+ contents : /* none */
30
+ | contents content
31
+
32
+ content : text
33
+ | aggregate
34
+ | value
35
+
36
+ aggregate : tag_from contents tag_to
37
+ value : tag_from text { data_value(val[1]) }
38
+
39
+ end
40
+
41
+ ---- header ----
42
+ #
43
+ # generated by racc
44
+ #
45
+ require File.dirname(__FILE__) + '/lexer'
46
+
47
+ ---- inner ----
48
+
49
+ attr :documents
50
+ attr :ofx_hashes
51
+
52
+ def initialize
53
+ @documents = []
54
+ @ofx_hashes = []
55
+
56
+ @tags_stack = []
57
+ end
58
+
59
+ private
60
+ def start_tag(tag_name)
61
+ # push an empty aggregate/value tag
62
+ @tags_stack.push({ tag_name => {} })
63
+ end
64
+
65
+ def data_value(value)
66
+ # pop the most recent tag, and use it's name as the key in the next higher tag's hash
67
+ tag = @tags_stack.pop
68
+ @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]] = CGI::unescapeHTML(value)
69
+ end
70
+
71
+ def end_tag(tag_name)
72
+ # pop the most recent tag, stop and recognize this as a full document if this is the last tag; otherwise,
73
+ # use this tag's naem as the key in the next higher tag's hash
74
+ tag = @tags_stack.pop
75
+ if (@tags_stack.empty?)
76
+ @ofx_hashes << tag
77
+ @documents << OFX::Document.from_ofx_102_hash(tag)
78
+ else
79
+ previous = @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]]
80
+ if previous == nil
81
+ @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]] = tag[tag.keys[0]]
82
+ elsif previous.kind_of?(Array)
83
+ previous << tag[tag.keys[0]]
84
+ else
85
+ @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]] = [previous, tag[tag.keys[0]]]
86
+ end
87
+ end
88
+ end
89
+
90
+ ---- footer ----