ofx 0.2.9 → 0.3.0

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.
@@ -1,29 +1,41 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ofx (0.2.8)
4
+ ofx (0.2.9)
5
5
  nokogiri
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
+ archive-tar-minitar (0.5.2)
11
+ columnize (0.3.2)
10
12
  diff-lcs (1.1.2)
11
- nokogiri (1.4.3.1)
12
- rspec (2.0.0)
13
- rspec-core (= 2.0.0)
14
- rspec-expectations (= 2.0.0)
15
- rspec-mocks (= 2.0.0)
16
- rspec-core (2.0.0)
17
- rspec-expectations (2.0.0)
18
- diff-lcs (>= 1.1.2)
19
- rspec-mocks (2.0.0)
20
- rspec-core (= 2.0.0)
21
- rspec-expectations (= 2.0.0)
13
+ linecache19 (0.5.11)
14
+ ruby_core_source (>= 0.1.4)
15
+ nokogiri (1.4.4)
16
+ rspec (2.4.0)
17
+ rspec-core (~> 2.4.0)
18
+ rspec-expectations (~> 2.4.0)
19
+ rspec-mocks (~> 2.4.0)
20
+ rspec-core (2.4.0)
21
+ rspec-expectations (2.4.0)
22
+ diff-lcs (~> 1.1.2)
23
+ rspec-mocks (2.4.0)
24
+ ruby-debug-base19 (0.11.24)
25
+ columnize (>= 0.3.1)
26
+ linecache19 (>= 0.5.11)
27
+ ruby_core_source (>= 0.1.4)
28
+ ruby-debug19 (0.11.6)
29
+ columnize (>= 0.3.1)
30
+ linecache19 (>= 0.5.11)
31
+ ruby-debug-base19 (>= 0.11.19)
32
+ ruby_core_source (0.1.4)
33
+ archive-tar-minitar (>= 0.5.2)
22
34
 
23
35
  PLATFORMS
24
36
  ruby
25
37
 
26
38
  DEPENDENCIES
27
- nokogiri
28
39
  ofx!
29
- rspec (>= 2.0.0)
40
+ rspec (~> 2.0)
41
+ ruby-debug19
data/lib/ofx.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "open-uri"
2
2
  require "nokogiri"
3
+ require "bigdecimal"
3
4
 
4
5
  require "iconv"
5
6
  require "kconv"
@@ -7,6 +8,7 @@ require "kconv"
7
8
  require "ofx/errors"
8
9
  require "ofx/parser"
9
10
  require "ofx/parser/ofx102"
11
+ require "ofx/parser/ofx211"
10
12
  require "ofx/foundation"
11
13
  require "ofx/balance"
12
14
  require "ofx/account"
@@ -7,9 +7,9 @@ module OFX
7
7
  attr_reader :parser
8
8
 
9
9
  def initialize(resource)
10
- resource = open_resource(resource)
11
- resource.rewind
12
- @content = convert_to_utf8(resource.read)
10
+ resource = open_resource(resource)
11
+ resource.rewind
12
+ @content = convert_to_utf8(resource.read)
13
13
 
14
14
  begin
15
15
  @headers, @body = prepare(content)
@@ -17,10 +17,11 @@ module OFX
17
17
  raise OFX::UnsupportedFileError
18
18
  end
19
19
 
20
-
21
- case @headers["VERSION"]
22
- when "102" then
23
- @parser = OFX::Parser::OFX102.new(:headers => headers, :body => body)
20
+ case headers["VERSION"]
21
+ when /102/ then
22
+ @parser = OFX102.new(:headers => headers, :body => body)
23
+ when /200|211/ then
24
+ @parser = OFX211.new(:headers => headers, :body => body)
24
25
  else
25
26
  raise OFX::UnsupportedFileError
26
27
  end
@@ -28,37 +29,34 @@ module OFX
28
29
 
29
30
  def open_resource(resource)
30
31
  if resource.respond_to?(:read)
31
- return resource
32
+ resource
32
33
  else
33
- begin
34
- return open(resource)
35
- rescue
36
- return StringIO.new(resource)
37
- end
34
+ open(resource)
38
35
  end
36
+ rescue Exception
37
+ StringIO.new(resource)
39
38
  end
40
39
 
41
40
  private
42
41
  def prepare(content)
43
- # Split headers & body
44
- headers, body = content.dup.split(/<OFX>/, 2)
45
-
46
- # Change single CR's to LF's to avoid issues with some banks
47
- headers.gsub!(/\r(?!\n)/, "\n")
42
+ # split headers & body
43
+ header_text, body = content.dup.split(/<OFX>/, 2)
48
44
 
49
45
  raise OFX::UnsupportedFileError unless body
50
46
 
51
- # Parse headers. When value is NONE, convert it to nil.
52
- headers = headers.to_enum(:each_line).inject({}) do |memo, line|
53
- _, key, value = *line.match(/^(.*?):(.*?)(\r?\n)*$/)
54
- memo[key] = value == "NONE" ? nil : value
55
- memo
47
+ # Header format is different between versions. Give each
48
+ # parser a chance to parse the headers.
49
+ headers = nil
50
+
51
+ OFX::Parser.constants.grep(/OFX/).each do |name|
52
+ headers = OFX::Parser.const_get(name).parse_headers(header_text)
53
+ break if headers
56
54
  end
57
55
 
58
56
  # Replace body tags to parse it with Nokogiri
59
- body.gsub!(/>\s+</m, '><')
60
- body.gsub!(/\s+</m, '<')
61
- body.gsub!(/>\s+/m, '>')
57
+ body.gsub!(/>\s+</m, "><")
58
+ body.gsub!(/\s+</m, "<")
59
+ body.gsub!(/>\s+/m, ">")
62
60
  body.gsub!(/<(\w+?)>([^<]+)/m, '<\1>\2</\1>')
63
61
 
64
62
  [headers, body]
@@ -66,8 +64,8 @@ module OFX
66
64
 
67
65
  def convert_to_utf8(string)
68
66
  return string if Kconv.isutf8(string)
69
- Iconv.conv('UTF-8', 'LATIN1//IGNORE', string)
67
+ Iconv.conv("UTF-8", "LATIN1//IGNORE", string)
70
68
  end
71
69
  end
72
70
  end
73
- end
71
+ end
@@ -1,5 +1,3 @@
1
- require "bigdecimal"
2
-
3
1
  module OFX
4
2
  module Parser
5
3
  class OFX102
@@ -33,6 +31,24 @@ module OFX
33
31
  @account ||= build_account
34
32
  end
35
33
 
34
+ def self.parse_headers(header_text)
35
+ # Change single CR's to LF's to avoid issues with some banks
36
+ header_text.gsub!(/\r(?!\n)/, "\n")
37
+
38
+ # Parse headers. When value is NONE, convert it to nil.
39
+ headers = header_text.to_enum(:each_line).inject({}) do |memo, line|
40
+ _, key, value = *line.match(/^(.*?):(.*?)\s*(\r?\n)*$/)
41
+
42
+ unless key.nil?
43
+ memo[key] = value == "NONE" ? nil : value
44
+ end
45
+
46
+ memo
47
+ end
48
+
49
+ return headers unless headers.empty?
50
+ end
51
+
36
52
  private
37
53
  def build_account
38
54
  OFX::Account.new({
@@ -57,6 +73,7 @@ module OFX
57
73
  :amount_in_pennies => (build_amount(element) * 100).to_i,
58
74
  :fit_id => element.search("fitid").inner_text,
59
75
  :memo => element.search("memo").inner_text,
76
+ :name => element.search("name").inner_text,
60
77
  :payee => element.search("payee").inner_text,
61
78
  :check_number => element.search("checknum").inner_text,
62
79
  :ref_number => element.search("refnum").inner_text,
@@ -0,0 +1,40 @@
1
+ module OFX
2
+ module Parser
3
+ class OFX211 < OFX102
4
+ VERSION = "2.1.1"
5
+
6
+ def self.parse_headers(header_text)
7
+ doc = Nokogiri::XML(header_text)
8
+
9
+ # Nokogiri can't search for processing instructions, so we
10
+ # need to do this manually.
11
+ doc.children.each do |e|
12
+ if e.type == Nokogiri::XML::Node::PI_NODE && e.name == "OFX"
13
+ # Getting the attributes from the element doesn't seem to
14
+ # work either.
15
+ return extract_headers(e.text)
16
+ end
17
+ end
18
+
19
+ nil
20
+ end
21
+
22
+ private
23
+ def self.extract_headers(text)
24
+ headers = {}
25
+ text.split(/\s+/).each do |attr_text|
26
+ match = /(.+)="(.+)"/.match(attr_text)
27
+ next unless match
28
+ k, v = match[1], match[2]
29
+ headers[k] = v
30
+ end
31
+ headers
32
+ end
33
+
34
+ def self.strip_quotes(s)
35
+ return unless s
36
+ s.sub(/^"(.*)"$/, '\1')
37
+ end
38
+ end
39
+ end
40
+ end
@@ -5,9 +5,10 @@ module OFX
5
5
  attr_accessor :check_number
6
6
  attr_accessor :fit_id
7
7
  attr_accessor :memo
8
+ attr_accessor :name
8
9
  attr_accessor :payee
9
10
  attr_accessor :posted_at
10
11
  attr_accessor :ref_number
11
12
  attr_accessor :type
12
13
  end
13
- end
14
+ end
@@ -1,8 +1,8 @@
1
1
  module OFX
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 2
5
- PATCH = 9
4
+ MINOR = 3
5
+ PATCH = 0
6
6
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
  end
8
8
  end
@@ -9,10 +9,10 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Nando Vieira"]
10
10
  s.email = ["fnando.vieira@gmail.com"]
11
11
  s.homepage = "http://rubygems.org/gems/ofx"
12
- s.summary = "A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports OFX 1.0.2."
12
+ s.summary = "A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports OFX 102, 200 and 211."
13
13
  s.description = <<-TXT
14
14
  A simple OFX (Open Financial Exchange) parser built on top of Nokogiri.
15
- Currently supports OFX 1.0.2.
15
+ Currently supports OFX 102, 200 and 211.
16
16
 
17
17
  Usage:
18
18
 
@@ -27,5 +27,6 @@ TXT
27
27
  s.require_paths = ["lib"]
28
28
 
29
29
  s.add_dependency "nokogiri"
30
- s.add_development_dependency "rspec", ">= 2.0.0"
30
+ s.add_development_dependency "rspec", "~> 2.0"
31
+ s.add_development_dependency "ruby-debug19"
31
32
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  OFXHEADER:100
4
4
  DATA:OFXSGML
5
- VERSION:200
5
+ VERSION:300
6
6
  SECURITY:NONE
7
7
  ENCODING:USASCII
8
8
  CHARSET:1252
@@ -71,6 +71,7 @@ NEWFILEUID:NONE
71
71
  <TRNAMT>-126.13
72
72
  <FITID>200910131
73
73
  <CHECKNUM>0001511
74
+ <NAME>Pagto conta telefone</NAME>
74
75
  <MEMO>TITULO COBRANCA-IB
75
76
  </STMTTRN><STMTTRN>
76
77
  <TRNTYPE>DEBIT
@@ -0,0 +1,85 @@
1
+ <?xml version="1.0" encoding="US-ASCII"?>
2
+
3
+ <!-- This example is taken from the 2.1.1 specification, pages 610-612 -->
4
+ <?OFX OFXHEADER="200" VERSION="211" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
5
+ <OFX>
6
+ <SIGNONMSGSRSV1>
7
+ <SONRS>
8
+ <STATUS>
9
+ <CODE>0</CODE>
10
+ <SEVERITY>INFO</SEVERITY>
11
+ </STATUS>
12
+ <DTSERVER>20050831165153.000[-8:PST]</DTSERVER>
13
+ <LANGUAGE>ENG</LANGUAGE>
14
+ </SONRS>
15
+ </SIGNONMSGSRSV1>
16
+ <BANKMSGSRSV1>
17
+ <STMTTRNRS>
18
+ <TRNUID>0</TRNUID>
19
+ <STATUS>
20
+ <CODE>0</CODE>
21
+ <SEVERITY>INFO</SEVERITY>
22
+ </STATUS>
23
+ <STMTRS>
24
+ <CURDEF>USD</CURDEF>
25
+ <BANKACCTFROM>
26
+ <BANKID>000000123</BANKID>
27
+ <ACCTID>123456</ACCTID>
28
+ <ACCTTYPE>CHECKING</ACCTTYPE>
29
+ </BANKACCTFROM>
30
+ <BANKTRANLIST>
31
+ <DTSTART>20040801</DTSTART>
32
+ <DTEND>20050831165153.000[-8:PST]</DTEND>
33
+ <STMTTRN>
34
+ <TRNTYPE>POS</TRNTYPE>
35
+ <DTPOSTED>20050824080000</DTPOSTED>
36
+ <TRNAMT>-80</TRNAMT>
37
+ <FITID>219378</FITID>
38
+ <NAME>FrogKick Scuba Gear</NAME>
39
+ </STMTTRN>
40
+ </BANKTRANLIST>
41
+ <LEDGERBAL>
42
+ <BALAMT>2156.56</BALAMT>
43
+ <DTASOF>20050831165153</DTASOF>
44
+ </LEDGERBAL>
45
+ </STMTRS>
46
+ </STMTTRNRS>
47
+ </BANKMSGSRSV1>
48
+ <CREDITCARDMSGSRSV1>
49
+ <CCSTMTTRNRS>
50
+ <TRNUID>0</TRNUID>
51
+ <STATUS>
52
+ <CODE>0</CODE>
53
+ <SEVERITY>INFO</SEVERITY>
54
+ </STATUS>
55
+ <CCSTMTRS>
56
+ <CURDEF>USD</CURDEF>
57
+ <CCACCTFROM>
58
+ <ACCTID>123412341234</ACCTID>
59
+ </CCACCTFROM>
60
+ <BANKTRANLIST>
61
+ <DTSTART>20050801</DTSTART>
62
+ <DTEND>20050831165153.000[-8:PST]</DTEND>
63
+ <STMTTRN>
64
+ <TRNTYPE>INT</TRNTYPE>
65
+ <DTPOSTED>20050811080000</DTPOSTED>
66
+ <TRNAMT>-23.00</TRNAMT>
67
+ <FITID>219867</FITID>
68
+ <NAME>Interest Charge</NAME>
69
+ </STMTTRN>
70
+ <STMTTRN>
71
+ <TRNTYPE>CREDIT</TRNTYPE>
72
+ <DTPOSTED>20050811080000</DTPOSTED>
73
+ <TRNAMT>350.00</TRNAMT>
74
+ <FITID>219868</FITID>
75
+ <NAME>Payment - Thank You</NAME>
76
+ </STMTTRN>
77
+ </BANKTRANLIST>
78
+ <LEDGERBAL>
79
+ <BALAMT>-562.00</BALAMT>
80
+ <DTASOF>20050831165153</DTASOF>
81
+ </LEDGERBAL>
82
+ </CCSTMTRS>
83
+ </CCSTMTTRNRS>
84
+ </CREDITCARDMSGSRSV1>
85
+ </OFX>
@@ -13,7 +13,12 @@ describe OFX::Parser::OFX102 do
13
13
  it "should set headers" do
14
14
  @parser.headers.should == @ofx.headers
15
15
  end
16
-
16
+
17
+ it "should trim trailing whitespace from headers" do
18
+ headers = OFX::Parser::OFX102.parse_headers("VERSION:102 ")
19
+ headers["VERSION"].should == "102"
20
+ end
21
+
17
22
  it "should set body" do
18
23
  @parser.body.should == @ofx.body
19
24
  end
@@ -21,4 +26,4 @@ describe OFX::Parser::OFX102 do
21
26
  it "should set account" do
22
27
  @parser.account.should be_a_kind_of(OFX::Account)
23
28
  end
24
- end
29
+ end
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+
3
+ describe OFX::Parser::OFX211 do
4
+ before do
5
+ @ofx = OFX::Parser::Base.new("spec/fixtures/v211.ofx")
6
+ @parser = @ofx.parser
7
+ end
8
+
9
+ it "should have a version" do
10
+ OFX::Parser::OFX211::VERSION.should == "2.1.1"
11
+ end
12
+
13
+ it "should set headers" do
14
+ @parser.headers.should == @ofx.headers
15
+ end
16
+
17
+ it "should set body" do
18
+ @parser.body.should == @ofx.body
19
+ end
20
+
21
+ it "should set account" do
22
+ @parser.account.should be_a_kind_of(OFX::Account)
23
+ end
24
+
25
+ context "transactions" do
26
+ before do
27
+ @transactions = @parser.account.transactions
28
+ end
29
+
30
+ # Test file contains only three transactions. Let's just check
31
+ # them all.
32
+ context "first" do
33
+ before do
34
+ @t = @transactions[0]
35
+ end
36
+
37
+ it "should contain the correct values" do
38
+ @t.amount.should == -80
39
+ @t.fit_id.should == "219378"
40
+ @t.memo.should be_empty
41
+ @t.posted_at.should == Time.parse("2005-08-24 08:00:00")
42
+ @t.name.should == "FrogKick Scuba Gear"
43
+ end
44
+ end
45
+
46
+ context "second" do
47
+ before do
48
+ @t = @transactions[1]
49
+ end
50
+
51
+ it "should contain the correct values" do
52
+ @t.amount.should == -23
53
+ @t.fit_id.should == "219867"
54
+ @t.memo.should be_empty
55
+ @t.posted_at.should == Time.parse("2005-08-11 08:00:00")
56
+ @t.name.should == "Interest Charge"
57
+ end
58
+ end
59
+
60
+ context "third" do
61
+ before do
62
+ @t = @transactions[2]
63
+ end
64
+
65
+ it "should contain the correct values" do
66
+ @t.amount.should == 350
67
+ @t.fit_id.should == "219868"
68
+ @t.memo.should be_empty
69
+ @t.posted_at.should == Time.parse("2005-08-11 08:00:00")
70
+ @t.name.should == "Payment - Thank You"
71
+ end
72
+ end
73
+ end
74
+ end
75
+
@@ -105,6 +105,16 @@ describe OFX::Transaction do
105
105
  end
106
106
  end
107
107
 
108
+ context "with name" do
109
+ before do
110
+ @transaction = @account.transactions[3]
111
+ end
112
+
113
+ it "should set name" do
114
+ @transaction.name.should == "Pagto conta telefone"
115
+ end
116
+ end
117
+
108
118
  context "with other types" do
109
119
 
110
120
  before do
@@ -132,7 +142,5 @@ describe OFX::Transaction do
132
142
  @transaction = @account.transactions[0]
133
143
  @transaction.type.should == :check
134
144
  end
135
-
136
145
  end
137
-
138
146
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 9
9
- version: 0.2.9
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Nando Vieira
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-10 00:00:00 -02:00
17
+ date: 2011-01-25 00:00:00 -02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -36,18 +36,30 @@ dependencies:
36
36
  requirement: &id002 !ruby/object:Gem::Requirement
37
37
  none: false
38
38
  requirements:
39
- - - ">="
39
+ - - ~>
40
40
  - !ruby/object:Gem::Version
41
41
  segments:
42
42
  - 2
43
43
  - 0
44
- - 0
45
- version: 2.0.0
44
+ version: "2.0"
46
45
  type: :development
47
46
  version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: ruby-debug19
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id003
48
60
  description: |
49
61
  A simple OFX (Open Financial Exchange) parser built on top of Nokogiri.
50
- Currently supports OFX 1.0.2.
62
+ Currently supports OFX 102, 200 and 211.
51
63
 
52
64
  Usage:
53
65
 
@@ -76,6 +88,7 @@ files:
76
88
  - lib/ofx/foundation.rb
77
89
  - lib/ofx/parser.rb
78
90
  - lib/ofx/parser/ofx102.rb
91
+ - lib/ofx/parser/ofx211.rb
79
92
  - lib/ofx/transaction.rb
80
93
  - lib/ofx/version.rb
81
94
  - ofx.gemspec
@@ -84,8 +97,10 @@ files:
84
97
  - spec/fixtures/invalid_version.ofx
85
98
  - spec/fixtures/sample.ofx
86
99
  - spec/fixtures/utf8.ofx
100
+ - spec/fixtures/v211.ofx
87
101
  - spec/ofx/account_spec.rb
88
102
  - spec/ofx/ofx102_spec.rb
103
+ - spec/ofx/ofx211_spec.rb
89
104
  - spec/ofx/ofx_parser_spec.rb
90
105
  - spec/ofx/ofx_spec.rb
91
106
  - spec/ofx/transaction_spec.rb
@@ -121,15 +136,17 @@ rubyforge_project:
121
136
  rubygems_version: 1.3.7
122
137
  signing_key:
123
138
  specification_version: 3
124
- summary: A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports OFX 1.0.2.
139
+ summary: A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports OFX 102, 200 and 211.
125
140
  test_files:
126
141
  - spec/fixtures/avatar.gif
127
142
  - spec/fixtures/bb.ofx
128
143
  - spec/fixtures/invalid_version.ofx
129
144
  - spec/fixtures/sample.ofx
130
145
  - spec/fixtures/utf8.ofx
146
+ - spec/fixtures/v211.ofx
131
147
  - spec/ofx/account_spec.rb
132
148
  - spec/ofx/ofx102_spec.rb
149
+ - spec/ofx/ofx211_spec.rb
133
150
  - spec/ofx/ofx_parser_spec.rb
134
151
  - spec/ofx/ofx_spec.rb
135
152
  - spec/ofx/transaction_spec.rb