ofx 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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