mt940_parser 1.0.5 → 1.2.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/VERSION.yml +4 -4
- data/lib/mt940/customer_statement_message.rb +15 -16
- data/lib/mt940.rb +35 -35
- data/mt940_parser.gemspec +6 -4
- data/test/test_mt940.rb +2 -11
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90c03b829e6f715e6255c89672b49fb07564e6de
|
4
|
+
data.tar.gz: 838b67c8f1e8552b2118fe2e224fb24c6f26341e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d062a25b89d4d24d9eaaea209630c4b2cec15b6929e5c17ea35401875c0d87fe89d227b1073c8a82af03402af42a90f4ca60007c00ee7456d7049ab3295a50e2
|
7
|
+
data.tar.gz: ff659e396c6f01a5978b27e513b2254bd6e93cf40d10c2bc9fd08bf82a9b00643dacbcddc11f3780eb83349bed90b682af8c971162fd4ac781afd7ad41dc771d
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p594
|
data/.travis.yml
ADDED
data/VERSION.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
---
|
1
|
+
---
|
2
2
|
:major: 1
|
3
|
-
:minor:
|
4
|
-
:
|
5
|
-
:
|
3
|
+
:minor: 2
|
4
|
+
:patch: 0
|
5
|
+
:build:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# this is a beautification wrapper around the low-level
|
2
|
-
# MT940.parse command. use it in order to make dealing with
|
2
|
+
# MT940.parse command. use it in order to make dealing with
|
3
3
|
# the data easier
|
4
4
|
class MT940
|
5
5
|
|
@@ -10,20 +10,19 @@ class MT940
|
|
10
10
|
def self.parse_file(file)
|
11
11
|
self.parse(File.read(file))
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def self.parse(data)
|
15
15
|
messages = MT940.parse(data)
|
16
16
|
messages.map { |msg| new(msg) }
|
17
17
|
end
|
18
|
-
|
19
|
-
def initialize(
|
20
|
-
@
|
21
|
-
@account = @raw.find { |line| line.class == MT940::Account }
|
18
|
+
|
19
|
+
def initialize(lines)
|
20
|
+
@account = lines.find { |line| line.class == MT940::Account }
|
22
21
|
@statement_lines = []
|
23
|
-
|
22
|
+
lines.each_with_index do |line, i|
|
24
23
|
next unless line.class == MT940::StatementLine
|
25
|
-
ensure_is_info_line!(
|
26
|
-
@statement_lines << StatementLineBundle.new(
|
24
|
+
ensure_is_info_line!(lines[i+1])
|
25
|
+
@statement_lines << StatementLineBundle.new(lines[i], lines[i+1])
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
@@ -35,16 +34,16 @@ class MT940
|
|
35
34
|
@account.account_number
|
36
35
|
end
|
37
36
|
|
38
|
-
private
|
39
|
-
|
37
|
+
private
|
38
|
+
|
40
39
|
def ensure_is_info_line!(line)
|
41
40
|
unless line.is_a?(MT940::StatementLineInformation)
|
42
|
-
raise StandardError, "Unexpected Structure; expected StatementLineInformation, but was #{line.class}"
|
41
|
+
raise StandardError, "Unexpected Structure; expected StatementLineInformation, but was #{line.class}"
|
43
42
|
end
|
44
43
|
end
|
45
|
-
|
44
|
+
|
46
45
|
end
|
47
|
-
|
46
|
+
|
48
47
|
class StatementLineBundle
|
49
48
|
|
50
49
|
METHOD_MAP = {
|
@@ -71,5 +70,5 @@ class MT940
|
|
71
70
|
end
|
72
71
|
|
73
72
|
end
|
74
|
-
|
75
|
-
end
|
73
|
+
|
74
|
+
end
|
data/lib/mt940.rb
CHANGED
@@ -3,10 +3,10 @@ require 'mt940/customer_statement_message'
|
|
3
3
|
class MT940
|
4
4
|
class Field
|
5
5
|
attr_reader :modifier, :content
|
6
|
-
|
6
|
+
|
7
7
|
DATE = /(\d{2})(\d{2})(\d{2})/
|
8
8
|
SHORT_DATE = /(\d{2})(\d{2})/
|
9
|
-
|
9
|
+
|
10
10
|
class << self
|
11
11
|
|
12
12
|
def for(line)
|
@@ -24,16 +24,16 @@ class MT940
|
|
24
24
|
'65' => FutureValutaBalance,
|
25
25
|
'86' => StatementLineInformation
|
26
26
|
}[number]
|
27
|
-
|
27
|
+
|
28
28
|
raise StandardError, "Field #{number} is not implemented" unless klass
|
29
|
-
|
29
|
+
|
30
30
|
klass.new(modifier, content)
|
31
31
|
else
|
32
32
|
raise StandardError, "Wrong line format: #{line.dump}"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def initialize(modifier, content)
|
38
38
|
@modifier = modifier
|
39
39
|
parse_content(content)
|
@@ -42,14 +42,14 @@ class MT940
|
|
42
42
|
private
|
43
43
|
def parse_amount_in_cents(amount)
|
44
44
|
# don't use Integer(amount) function, because amount can be "008" - interpreted as octal number ("010" = 8)
|
45
|
-
amount.gsub(',', '').to_i
|
45
|
+
amount.gsub(',', '').to_i
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def parse_date(date)
|
49
49
|
date.match(DATE)
|
50
50
|
Date.new("20#{$1}".to_i, $2.to_i, $3.to_i)
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def parse_entry_date(raw_entry_date, value_date)
|
54
54
|
raw_entry_date.match(SHORT_DATE)
|
55
55
|
entry_date = Date.new(value_date.year, $1.to_i, $2.to_i)
|
@@ -63,16 +63,16 @@ class MT940
|
|
63
63
|
# 20
|
64
64
|
class Job < Field
|
65
65
|
attr_reader :reference
|
66
|
-
|
66
|
+
|
67
67
|
def parse_content(content)
|
68
68
|
@reference = content
|
69
69
|
end
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
# 21
|
73
73
|
class Reference < Job
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
# 25
|
77
77
|
class Account < Field
|
78
78
|
attr_reader :bank_code, :account_number, :account_currency
|
@@ -84,13 +84,13 @@ class MT940
|
|
84
84
|
@bank_code, @account_number, @account_currency = $1, $2, $3
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
# 28
|
89
89
|
class Statement < Field
|
90
90
|
attr_reader :number, :sheet
|
91
91
|
|
92
92
|
CONTENT = /^(0|(\d{5,5})\/(\d{2,5}))$/
|
93
|
-
|
93
|
+
|
94
94
|
def parse_content(content)
|
95
95
|
content.match(CONTENT)
|
96
96
|
if $1 == '0'
|
@@ -100,13 +100,13 @@ class MT940
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
# 60
|
105
105
|
class AccountBalance < Field
|
106
106
|
attr_reader :balance_type, :sign, :currency, :amount, :date
|
107
107
|
|
108
108
|
CONTENT = /^(C|D)(\w{6})(\w{3})(\d{1,12},\d{0,2})$/
|
109
|
-
|
109
|
+
|
110
110
|
def parse_content(content)
|
111
111
|
content.match(CONTENT)
|
112
112
|
|
@@ -116,27 +116,27 @@ class MT940
|
|
116
116
|
when 'M'
|
117
117
|
:intermediate
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
@sign = case $1
|
121
121
|
when 'C'
|
122
122
|
:credit
|
123
123
|
when 'D'
|
124
124
|
:debit
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
raw_date = $2
|
128
128
|
@currency = $3
|
129
129
|
@amount = parse_amount_in_cents($4)
|
130
|
-
|
130
|
+
|
131
131
|
@date = case raw_date
|
132
132
|
when 'ALT', '0'
|
133
133
|
nil
|
134
134
|
when DATE
|
135
|
-
Date.new("20#{$1}".to_i, $2.to_i, $3.to_i)
|
135
|
+
Date.new("20#{$1}".to_i, $2.to_i, $3.to_i)
|
136
136
|
end
|
137
137
|
end
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
# 61
|
141
141
|
class StatementLine < Field
|
142
142
|
attr_reader :date, :entry_date, :funds_code, :amount, :swift_code, :reference, :transaction_description
|
@@ -145,7 +145,7 @@ class MT940
|
|
145
145
|
|
146
146
|
def parse_content(content)
|
147
147
|
content.match(CONTENT)
|
148
|
-
|
148
|
+
|
149
149
|
raw_date = $1
|
150
150
|
raw_entry_date = $2
|
151
151
|
@funds_code = case $3
|
@@ -158,25 +158,25 @@ class MT940
|
|
158
158
|
when 'RD'
|
159
159
|
:return_debit
|
160
160
|
end
|
161
|
-
|
161
|
+
|
162
162
|
@amount = parse_amount_in_cents($4)
|
163
163
|
@swift_code = $5
|
164
164
|
@reference = $6
|
165
165
|
@transaction_description = $7
|
166
|
-
|
166
|
+
|
167
167
|
@date = parse_date(raw_date)
|
168
168
|
@entry_date = parse_entry_date(raw_entry_date, @date) if raw_entry_date
|
169
169
|
end
|
170
|
-
|
170
|
+
|
171
171
|
def value_date
|
172
172
|
@date
|
173
173
|
end
|
174
174
|
end
|
175
|
-
|
175
|
+
|
176
176
|
# 62
|
177
177
|
class ClosingBalance < AccountBalance
|
178
178
|
end
|
179
|
-
|
179
|
+
|
180
180
|
# 64
|
181
181
|
class ValutaBalance < AccountBalance
|
182
182
|
end
|
@@ -184,23 +184,23 @@ class MT940
|
|
184
184
|
# 65
|
185
185
|
class FutureValutaBalance < AccountBalance
|
186
186
|
end
|
187
|
-
|
187
|
+
|
188
188
|
# 86
|
189
189
|
class StatementLineInformation < Field
|
190
|
-
attr_reader :code, :transaction_description, :prima_nota, :details, :bank_code, :account_number,
|
190
|
+
attr_reader :code, :transaction_description, :prima_nota, :details, :bank_code, :account_number,
|
191
191
|
:account_holder, :text_key_extension, :not_implemented_fields
|
192
|
-
|
192
|
+
|
193
193
|
def parse_content(content)
|
194
194
|
content.match(/^(\d{3})((.).*)$/)
|
195
195
|
@code = $1.to_i
|
196
|
-
|
196
|
+
|
197
197
|
details = []
|
198
198
|
account_holder = []
|
199
199
|
|
200
200
|
if seperator = $3
|
201
201
|
sub_fields = $2.scan(/#{Regexp.escape(seperator)}(\d{2})([^#{Regexp.escape(seperator)}]*)/)
|
202
|
-
|
203
|
-
|
202
|
+
|
203
|
+
|
204
204
|
sub_fields.each do |(code, content)|
|
205
205
|
case code.to_i
|
206
206
|
when 0
|
@@ -225,19 +225,19 @@ class MT940
|
|
225
225
|
end
|
226
226
|
end
|
227
227
|
end
|
228
|
-
|
228
|
+
|
229
229
|
@details = details.join("\n")
|
230
230
|
@account_holder = account_holder.join("\n")
|
231
231
|
end
|
232
232
|
end
|
233
|
-
|
233
|
+
|
234
234
|
|
235
235
|
class << self
|
236
236
|
def parse(text)
|
237
237
|
raise "Invalid encoding!" unless text.valid_encoding?
|
238
238
|
new_text = text.encode('utf-8').strip
|
239
239
|
new_text << "\r\n" if new_text[-1,1] == '-'
|
240
|
-
raw_sheets = new_text.split(/^-\r\n/).map { |sheet| sheet.gsub(/\r\n(?!:)/, '') }
|
240
|
+
raw_sheets = new_text.split(/^-\s*\r\n/).map { |sheet| sheet.gsub(/\r\n(?!:)/, '') }
|
241
241
|
sheets = raw_sheets.map { |raw_sheet| parse_sheet(raw_sheet) }
|
242
242
|
end
|
243
243
|
|
data/mt940_parser.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "mt940_parser"
|
8
|
-
s.version = "1.0
|
8
|
+
s.version = "1.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Thies C. Arntzen", "Phillip Oertel"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2015-01-07"
|
13
13
|
s.email = "developers@betterplace.org"
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
@@ -17,7 +17,9 @@ Gem::Specification.new do |s|
|
|
17
17
|
]
|
18
18
|
s.files = [
|
19
19
|
".document",
|
20
|
+
".ruby-version",
|
20
21
|
".specification",
|
22
|
+
".travis.yml",
|
21
23
|
"Gemfile",
|
22
24
|
"Gemfile.lock",
|
23
25
|
"LICENSE",
|
@@ -58,11 +60,11 @@ Gem::Specification.new do |s|
|
|
58
60
|
s.homepage = "http://github.com/betterplace/mt940_parser"
|
59
61
|
s.licenses = ["MIT"]
|
60
62
|
s.require_paths = ["lib"]
|
61
|
-
s.rubygems_version = "
|
63
|
+
s.rubygems_version = "2.0.14"
|
62
64
|
s.summary = "MT940 parses account statements in the SWIFT MT940 format."
|
63
65
|
|
64
66
|
if s.respond_to? :specification_version then
|
65
|
-
s.specification_version =
|
67
|
+
s.specification_version = 4
|
66
68
|
|
67
69
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
68
70
|
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
data/test/test_mt940.rb
CHANGED
@@ -6,18 +6,9 @@ YAML::ENGINE.yamler = 'psych'
|
|
6
6
|
# $DEBUG = true
|
7
7
|
class TestMt940 < Test::Unit::TestCase
|
8
8
|
|
9
|
-
def read_mt940_data(file)
|
10
|
-
MT940.parse(IO.read(file))
|
11
|
-
end
|
12
|
-
|
13
|
-
def writeout(name, data)
|
14
|
-
|
15
|
-
File.open(name, "w") { |f| f.write data }
|
16
|
-
end
|
17
|
-
|
18
9
|
def test_it_should_parse_fixture_files_correctly
|
19
10
|
Dir[File.dirname(__FILE__) + "/fixtures/*.txt"].reject { |f| f =~ /sepa_snippet/ }.each do |file|
|
20
|
-
data =
|
11
|
+
data = MT940.parse(IO.read(file))
|
21
12
|
generated_structure_file = file.gsub(/.txt$/, ".yml")
|
22
13
|
|
23
14
|
assert_equal YAML::load_file(generated_structure_file).to_yaml, data.to_yaml
|
@@ -33,4 +24,4 @@ class TestMt940 < Test::Unit::TestCase
|
|
33
24
|
end
|
34
25
|
end
|
35
26
|
|
36
|
-
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mt940_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thies C. Arntzen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -62,7 +62,9 @@ extra_rdoc_files:
|
|
62
62
|
- README.rdoc
|
63
63
|
files:
|
64
64
|
- .document
|
65
|
+
- .ruby-version
|
65
66
|
- .specification
|
67
|
+
- .travis.yml
|
66
68
|
- Gemfile
|
67
69
|
- Gemfile.lock
|
68
70
|
- LICENSE
|
@@ -119,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
121
|
version: '0'
|
120
122
|
requirements: []
|
121
123
|
rubyforge_project:
|
122
|
-
rubygems_version: 2.
|
124
|
+
rubygems_version: 2.0.14
|
123
125
|
signing_key:
|
124
126
|
specification_version: 4
|
125
127
|
summary: MT940 parses account statements in the SWIFT MT940 format.
|