mt940parser 0.1.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/mt940parser.rb +6 -0
- data/lib/mt940parser/document.treetop +83 -0
- data/lib/mt940parser/node_extensions.rb +97 -0
- data/lib/mt940parser/parser.rb +38 -0
- data/lib/mt940parser/r86.treetop +65 -0
- data/lib/mt940parser/version.rb +3 -0
- data/mt940parser.gemspec +26 -0
- data/spec/functional/hallo_spec.rb +9 -0
- data/spec/spec_helper.rb +13 -0
- metadata +128 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 georgi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Mt940parser
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'mt940parser'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install mt940parser
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/mt940parser.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
grammar Document
|
2
|
+
include R86
|
3
|
+
|
4
|
+
rule root
|
5
|
+
record* <All>
|
6
|
+
end
|
7
|
+
|
8
|
+
rule record
|
9
|
+
date account_iban statement_number initial_balance transactions final_balance wololo? <Record>
|
10
|
+
end
|
11
|
+
|
12
|
+
rule date
|
13
|
+
codess (!(nl) .)* nl <Date>
|
14
|
+
end
|
15
|
+
|
16
|
+
rule account_iban #:25:
|
17
|
+
codess (!(nl) .)* nl <NotImplemented>
|
18
|
+
end
|
19
|
+
|
20
|
+
rule statement_number
|
21
|
+
codess (!(nl) .)* nl <NotImplemented>
|
22
|
+
# (":28:" / ":28C:") (!(nl) .)* <NotImplemented>
|
23
|
+
end
|
24
|
+
|
25
|
+
rule initial_balance #:60F:
|
26
|
+
codess (!(nl) .)* nl <NotImplemented>
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create captures for those if needed (not for now)
|
30
|
+
rule final_balance
|
31
|
+
":62F:" (!(nl) .)* nl
|
32
|
+
end
|
33
|
+
|
34
|
+
rule wololo
|
35
|
+
":64:" (!('-' / nl) .)*
|
36
|
+
end
|
37
|
+
|
38
|
+
rule codess
|
39
|
+
(':61:' <Cods> / ":20:" <Cods> / ":28:" <Cods> / ":28C:" <Cods> / ":25:" <Cods> / ":60F:" <Cods> )
|
40
|
+
end
|
41
|
+
|
42
|
+
rule eof
|
43
|
+
.*
|
44
|
+
end
|
45
|
+
|
46
|
+
rule transactions
|
47
|
+
(transaction)* <Transactions>
|
48
|
+
end
|
49
|
+
|
50
|
+
rule transaction
|
51
|
+
base statement nl <Transaction>
|
52
|
+
end
|
53
|
+
|
54
|
+
rule base
|
55
|
+
# ":61:" valor date debit amount swift '//' reference (.)*
|
56
|
+
codess valor account_date type amount swift (!(nl) .)* nl <Base>
|
57
|
+
end
|
58
|
+
# ========= Base ======== #
|
59
|
+
rule valor
|
60
|
+
[\d] 6..6 <Valor>
|
61
|
+
end
|
62
|
+
|
63
|
+
rule account_date
|
64
|
+
[\d] 4..4 <AccountDate>
|
65
|
+
end
|
66
|
+
|
67
|
+
rule type
|
68
|
+
'C' <Type> / 'D' <Type>
|
69
|
+
end
|
70
|
+
|
71
|
+
rule amount
|
72
|
+
(![a-zA-Z] .) ..15 <Amount>
|
73
|
+
end
|
74
|
+
|
75
|
+
rule swift
|
76
|
+
[a-zA-Z] 4..4 <NotImplemented>
|
77
|
+
end
|
78
|
+
|
79
|
+
rule nl
|
80
|
+
[\n] <NewLine>
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Document
|
2
|
+
|
3
|
+
class Transaction < Treetop::Runtime::SyntaxNode
|
4
|
+
def details
|
5
|
+
elements.last.elements.last.text_value
|
6
|
+
end
|
7
|
+
|
8
|
+
def policy_numbers
|
9
|
+
details.scan(/\d{3,}/)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class All < Treetop::Runtime::SyntaxNode
|
14
|
+
def all_transactions
|
15
|
+
elements.map(&:t).map(&:elements).flatten
|
16
|
+
end
|
17
|
+
|
18
|
+
def all_policy_numbers
|
19
|
+
all_transactions.map(&:policy_numbers).reject(&:empty?)
|
20
|
+
end
|
21
|
+
|
22
|
+
def all_credit
|
23
|
+
all_transactions.select { |t| t.base.type.text_value == "C" }
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_debit
|
27
|
+
all_transactions.select { |t| t.base.type.text_value == "D" }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Utility
|
31
|
+
def a
|
32
|
+
all_transactions.sample
|
33
|
+
end
|
34
|
+
end
|
35
|
+
class Transactions < Treetop::Runtime::SyntaxNode
|
36
|
+
end
|
37
|
+
class Statement < Treetop::Runtime::SyntaxNode
|
38
|
+
end
|
39
|
+
class Record < Treetop::Runtime::SyntaxNode
|
40
|
+
def t
|
41
|
+
elements.last
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Base < Treetop::Runtime::SyntaxNode
|
46
|
+
end
|
47
|
+
class Digit < Treetop::Runtime::SyntaxNode
|
48
|
+
end
|
49
|
+
class All < Treetop::Runtime::SyntaxNode
|
50
|
+
end
|
51
|
+
class Cods < Treetop::Runtime::SyntaxNode
|
52
|
+
end
|
53
|
+
class Valor < Treetop::Runtime::SyntaxNode
|
54
|
+
end
|
55
|
+
class AccountDate < Treetop::Runtime::SyntaxNode
|
56
|
+
end
|
57
|
+
class Type < Treetop::Runtime::SyntaxNode
|
58
|
+
end
|
59
|
+
class Amount < Treetop::Runtime::SyntaxNode
|
60
|
+
def val
|
61
|
+
text_value.sub(',','.').to_f
|
62
|
+
end
|
63
|
+
end
|
64
|
+
class NotImplemented < Treetop::Runtime::SyntaxNode
|
65
|
+
end
|
66
|
+
class NewLine < Treetop::Runtime::SyntaxNode
|
67
|
+
end
|
68
|
+
|
69
|
+
class Date < Treetop::Runtime::SyntaxNode
|
70
|
+
def val
|
71
|
+
text_value.strip[4..-1]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
module R86
|
78
|
+
class NotImplemented < Treetop::Runtime::SyntaxNode
|
79
|
+
end
|
80
|
+
class Mark < Treetop::Runtime::SyntaxNode
|
81
|
+
end
|
82
|
+
class Description < Treetop::Runtime::SyntaxNode
|
83
|
+
end
|
84
|
+
class Details < Treetop::Runtime::SyntaxNode
|
85
|
+
end
|
86
|
+
class Detail < Treetop::Runtime::SyntaxNode
|
87
|
+
end
|
88
|
+
class Statement < Treetop::Runtime::SyntaxNode
|
89
|
+
end
|
90
|
+
class Code < Treetop::Runtime::SyntaxNode
|
91
|
+
end
|
92
|
+
class NewLine < Treetop::Runtime::SyntaxNode
|
93
|
+
end
|
94
|
+
class Blank < Treetop::Runtime::SyntaxNode
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MT940
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
base_path = File.dirname(__FILE__)
|
5
|
+
Treetop.load(File.expand_path(File.join(base_path, 'r86.treetop')))
|
6
|
+
Treetop.load(File.expand_path(File.join(base_path, 'document.treetop')))
|
7
|
+
require File.join(base_path, 'node_extensions')
|
8
|
+
|
9
|
+
@@parser = DocumentParser.new
|
10
|
+
|
11
|
+
def self.parse(data)
|
12
|
+
tree = @@parser.parse(data)
|
13
|
+
|
14
|
+
self.show_error(data) if tree.nil?
|
15
|
+
|
16
|
+
# clean up the tree by removing all nodes of default type 'SyntaxNode'
|
17
|
+
self.clean_tree(tree)
|
18
|
+
|
19
|
+
return tree
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def self.show_error(data)
|
25
|
+
@@parser.failure_reason =~ /^(Expected .+) after/m
|
26
|
+
puts "#{$1.gsub("\n", '$NEWLINE')}:", data.lines.to_a[@@parser.failure_line - 1],"#{'~' * (@@parser.failure_column - 1)}^"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.clean_tree(root_node)
|
30
|
+
return if(root_node.elements.nil?)
|
31
|
+
root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" }
|
32
|
+
root_node.elements.delete_if{|node| node.class.name == "Mt940::NewLine" } #remove nls
|
33
|
+
root_node.elements.delete_if{|node| node.class.name == "R86::Blank" } #remove nls
|
34
|
+
root_node.elements.each {|node| self.clean_tree(node) }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
grammar R86
|
2
|
+
|
3
|
+
rule statement
|
4
|
+
mark code description details bank_code account_number name <Statement>
|
5
|
+
end
|
6
|
+
|
7
|
+
rule mark
|
8
|
+
":86:" <Mark>
|
9
|
+
end
|
10
|
+
|
11
|
+
rule code
|
12
|
+
(!(codes) .)* <Code> / '' <Blank>
|
13
|
+
end
|
14
|
+
|
15
|
+
rule description
|
16
|
+
s '00' (!(codes) .)* <Description> / '' <Blank>
|
17
|
+
end
|
18
|
+
|
19
|
+
rule details
|
20
|
+
details0? details1 details2 details3 <Details>
|
21
|
+
end
|
22
|
+
|
23
|
+
rule details0
|
24
|
+
s '20' (!(codes) .)* <Detail> / '' <Blank>
|
25
|
+
end
|
26
|
+
|
27
|
+
rule details1
|
28
|
+
s '21' (!(codes) .)* <Detail> / '' <Blank>
|
29
|
+
end
|
30
|
+
|
31
|
+
rule details2
|
32
|
+
s '22' (!(codes) .)* <Detail> / '' <Blank>
|
33
|
+
end
|
34
|
+
|
35
|
+
rule details3
|
36
|
+
s '23' (!(codes) .)* <Detail> / '' <Blank>
|
37
|
+
end
|
38
|
+
rule bank_code
|
39
|
+
s '30' (!(codes) .)* <NotImplemented> / '' <Blank>
|
40
|
+
end
|
41
|
+
rule account_number
|
42
|
+
s '31' (!(codes) .)* <NotImplemented> / '' <Blank>
|
43
|
+
end
|
44
|
+
|
45
|
+
rule name
|
46
|
+
s '32' (!(nl) .)* <NotImplemented> / s '38' (!(nl) .)* <NotImplemented> / '' <Blank>
|
47
|
+
end
|
48
|
+
# rule smth
|
49
|
+
# s '38' (!(nl) .)* / ''
|
50
|
+
# end
|
51
|
+
|
52
|
+
rule codes
|
53
|
+
s '00' / s '10' / s '20' / s '21' / s '22' / s '23' / s '24' / s '25' / s '26'
|
54
|
+
/ s '27' / s '30' / s '31' / s '32' / s '33' / s '38'
|
55
|
+
end
|
56
|
+
|
57
|
+
rule s
|
58
|
+
'^' / '+'
|
59
|
+
end
|
60
|
+
|
61
|
+
rule nl
|
62
|
+
[\n] <NewLine>
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/mt940parser.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mt940parser/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mt940parser"
|
8
|
+
spec.version = MT940::VERSION
|
9
|
+
spec.authors = ["Georgi Mitrev"]
|
10
|
+
spec.email = ["gvmitrev@gmail.com"]
|
11
|
+
spec.description = %q{MT940 parser}
|
12
|
+
spec.summary = %q{Parser for the (SWIFT)-MT940 format}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "treetop"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'mt940parser'
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
5
|
+
config.run_all_when_everything_filtered = true
|
6
|
+
config.filter_run :focus
|
7
|
+
|
8
|
+
# Run specs in random order to surface order dependencies. If you find an
|
9
|
+
# order dependency and want to debug it, you can fix the order by providing
|
10
|
+
# the seed, which is printed after each run.
|
11
|
+
# --seed 1234
|
12
|
+
config.order = 'random'
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mt940parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Georgi Mitrev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: treetop
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: MT940 parser
|
79
|
+
email:
|
80
|
+
- gvmitrev@gmail.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- .rspec
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- lib/mt940parser.rb
|
92
|
+
- lib/mt940parser/document.treetop
|
93
|
+
- lib/mt940parser/node_extensions.rb
|
94
|
+
- lib/mt940parser/parser.rb
|
95
|
+
- lib/mt940parser/r86.treetop
|
96
|
+
- lib/mt940parser/version.rb
|
97
|
+
- mt940parser.gemspec
|
98
|
+
- spec/functional/hallo_spec.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
homepage: ''
|
101
|
+
licenses:
|
102
|
+
- MIT
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 1.8.23
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Parser for the (SWIFT)-MT940 format
|
125
|
+
test_files:
|
126
|
+
- spec/functional/hallo_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
has_rdoc:
|