cmxl 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +136 -0
- data/Rakefile +7 -0
- data/cmxl.gemspec +24 -0
- data/lib/cmxl.rb +20 -0
- data/lib/cmxl/field.rb +78 -0
- data/lib/cmxl/fields/account_balance.rb +45 -0
- data/lib/cmxl/fields/account_identification.rb +12 -0
- data/lib/cmxl/fields/available_balance.rb +8 -0
- data/lib/cmxl/fields/closing_balance.rb +8 -0
- data/lib/cmxl/fields/reference.rb +21 -0
- data/lib/cmxl/fields/statement_details.rb +54 -0
- data/lib/cmxl/fields/statement_line.rb +47 -0
- data/lib/cmxl/fields/statement_number.rb +8 -0
- data/lib/cmxl/statement.rb +84 -0
- data/lib/cmxl/transaction.rb +91 -0
- data/lib/cmxl/version.rb +3 -0
- data/spec/fields/account_balance_spec.rb +41 -0
- data/spec/fields/account_identification_spec.rb +24 -0
- data/spec/fields/available_balance_spec.rb +16 -0
- data/spec/fields/closing_balance_spec.rb +15 -0
- data/spec/fields/reference_spec.rb +12 -0
- data/spec/fields/statement_details_spec.rb +40 -0
- data/spec/fields/statement_number_spec.rb +10 -0
- data/spec/fields/statment_line_spec.rb +19 -0
- data/spec/fields/unknown_spec.rb +9 -0
- data/spec/fixtures/lines/account_balance_credit.txt +1 -0
- data/spec/fixtures/lines/account_balance_debit.txt +1 -0
- data/spec/fixtures/lines/account_identification_iban.txt +1 -0
- data/spec/fixtures/lines/account_identification_legacy.txt +1 -0
- data/spec/fixtures/lines/available_balance.txt +1 -0
- data/spec/fixtures/lines/closing_balance.txt +1 -0
- data/spec/fixtures/lines/reference.txt +1 -0
- data/spec/fixtures/lines/statement_details.txt +1 -0
- data/spec/fixtures/lines/statement_line.txt +1 -0
- data/spec/fixtures/lines/statement_number.txt +1 -0
- data/spec/fixtures/mt940.txt +75 -0
- data/spec/mt940_parsing_spec.rb +44 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/fixtures.rb +7 -0
- data/spec/transaction_spec.rb +28 -0
- metadata +155 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module Cmxl
|
2
|
+
module Fields
|
3
|
+
class StatementLine < Field
|
4
|
+
self.tag = 61
|
5
|
+
self.parser = /\A(?<date>\d{6})(?<entry_date>\d{4})?(?<funds_code>[a-zA-Z])(?<currency_letter>[a-zA-Z])\D?(?<amount>\d{1,12},\d{0,2})(?<swift_code>(?:N|F).{3})(?<reference>NONREF|.{0,16})(?:$|\/\/)(?<bank_reference>.*)/i
|
6
|
+
|
7
|
+
def credit?
|
8
|
+
self.data['funds_code'].to_s.upcase == 'C'
|
9
|
+
end
|
10
|
+
|
11
|
+
def debit?
|
12
|
+
!credit?
|
13
|
+
end
|
14
|
+
|
15
|
+
def sign
|
16
|
+
self.credit? ? 1 : -1
|
17
|
+
end
|
18
|
+
|
19
|
+
def amount
|
20
|
+
self.data['amount'].gsub(',','.').to_f * sign
|
21
|
+
end
|
22
|
+
|
23
|
+
def amount_in_cents
|
24
|
+
self.data['amount'].gsub(',', '').gsub('.','').to_i * sign
|
25
|
+
end
|
26
|
+
|
27
|
+
def date
|
28
|
+
to_date(self.data['date'])
|
29
|
+
end
|
30
|
+
def entry_date
|
31
|
+
to_date(self.data['entry_date'], self.date.year)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
super.merge({
|
36
|
+
'date' => date,
|
37
|
+
'entry_date' => entry_date,
|
38
|
+
'amount' => amount,
|
39
|
+
'amount_in_cents' => amount_in_cents,
|
40
|
+
'sign' => sign,
|
41
|
+
'debit' => debit?,
|
42
|
+
'credit' => credit?
|
43
|
+
})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
module Cmxl
|
3
|
+
class Statement
|
4
|
+
attr_accessor :source, :collection, :transactions, :fields, :lines
|
5
|
+
|
6
|
+
def initialize(source)
|
7
|
+
self.source = source
|
8
|
+
self.fields = []
|
9
|
+
self.lines = []
|
10
|
+
self.transactions = []
|
11
|
+
self.parse!
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse!
|
15
|
+
# first we clean uo the source and make concat wraped lines
|
16
|
+
self.source.split("\n").each do |line|
|
17
|
+
if line.start_with?(':') || self.lines.last.nil?
|
18
|
+
self.lines << line.strip
|
19
|
+
else
|
20
|
+
self.lines.last << line.strip
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# now we check each line. if it is part of a transaction we initate or update a transaction else we parse the field and add it to the fields collection
|
24
|
+
self.lines.each do |line|
|
25
|
+
if line.match(/\A:61:/)
|
26
|
+
self.transactions << Cmxl::Transaction.new(line)
|
27
|
+
elsif line.match(/\A:86:/) && !self.transactions.last.nil?
|
28
|
+
self.transactions.last.details = line
|
29
|
+
else
|
30
|
+
self.fields << Field.parse(line)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def sha
|
36
|
+
Digest::SHA2.new.update(self.source).to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def reference
|
40
|
+
self.field(20).reference
|
41
|
+
end
|
42
|
+
|
43
|
+
def generation_date
|
44
|
+
self.field(20).date
|
45
|
+
end
|
46
|
+
|
47
|
+
def account_identification
|
48
|
+
self.field(25)
|
49
|
+
end
|
50
|
+
|
51
|
+
def opening_balance
|
52
|
+
self.field(60, 'F')
|
53
|
+
end
|
54
|
+
|
55
|
+
def closing_balance
|
56
|
+
self.field(62, 'F')
|
57
|
+
end
|
58
|
+
|
59
|
+
def available_balance
|
60
|
+
self.field(64)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_h
|
64
|
+
{
|
65
|
+
'reference' => reference,
|
66
|
+
'sha' => sha,
|
67
|
+
'generation_date' => generation_date,
|
68
|
+
'account_identification' => account_identification,
|
69
|
+
'opening_balance' => opening_balance,
|
70
|
+
'closing_balance' => closing_balance,
|
71
|
+
'available_balance' => available_balance,
|
72
|
+
'transactions' => transactions
|
73
|
+
}
|
74
|
+
end
|
75
|
+
alias :to_hash :to_h
|
76
|
+
def to_json(*args)
|
77
|
+
to_h.to_json(*args)
|
78
|
+
end
|
79
|
+
|
80
|
+
def field(tag, modifier=nil)
|
81
|
+
self.fields.detect {|field| field.tag == tag.to_s && (modifier.nil? || field.modifier == modifier) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Cmxl
|
2
|
+
class Transaction
|
3
|
+
attr_accessor :statement_line, :details
|
4
|
+
|
5
|
+
def initialize(statement_line, details = nil)
|
6
|
+
self.statement_line = statement_line
|
7
|
+
self.details = details
|
8
|
+
end
|
9
|
+
|
10
|
+
def statement_line=(line)
|
11
|
+
@statement_line = Cmxl::Fields::StatementLine.parse(line) unless line.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def details=(line)
|
15
|
+
@details = Cmxl::Fields::StatementDetails.parse(line) unless line.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def debit?
|
19
|
+
self.statement_line.debit?
|
20
|
+
end
|
21
|
+
def credit?
|
22
|
+
self.statement_line.credit?
|
23
|
+
end
|
24
|
+
def amount
|
25
|
+
self.statement_line.amount
|
26
|
+
end
|
27
|
+
def sign
|
28
|
+
self.statement_line.sign
|
29
|
+
end
|
30
|
+
def funds_code
|
31
|
+
self.statement_line.funds_code
|
32
|
+
end
|
33
|
+
def amount_in_cents
|
34
|
+
self.statement_line.amount_in_cents
|
35
|
+
end
|
36
|
+
def date
|
37
|
+
self.statement_line.date
|
38
|
+
end
|
39
|
+
def entry_date
|
40
|
+
self.statement_line.entry_date
|
41
|
+
end
|
42
|
+
def funds_code
|
43
|
+
self.statement_line.funds_code
|
44
|
+
end
|
45
|
+
def currency_letter
|
46
|
+
self.statement_line.currency_letter
|
47
|
+
end
|
48
|
+
def swift_code
|
49
|
+
self.statement_line.swift_code
|
50
|
+
end
|
51
|
+
def reference
|
52
|
+
self.statement_line.reference
|
53
|
+
end
|
54
|
+
def bank_reference
|
55
|
+
self.statement_line.bank_reference
|
56
|
+
end
|
57
|
+
def description
|
58
|
+
self.details.description if self.details
|
59
|
+
end
|
60
|
+
def information
|
61
|
+
self.details.information if self.details
|
62
|
+
end
|
63
|
+
def bic
|
64
|
+
self.details.bic if self.details
|
65
|
+
end
|
66
|
+
def name
|
67
|
+
self.details.name if self.details
|
68
|
+
end
|
69
|
+
def iban
|
70
|
+
self.details.iban if self.details
|
71
|
+
end
|
72
|
+
def sepa
|
73
|
+
self.details.sepa if self.details
|
74
|
+
end
|
75
|
+
def sub_fields
|
76
|
+
self.details.sub_fields if self.details
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_h
|
80
|
+
{}.tap do |h|
|
81
|
+
h.merge!(self.statement_line.to_h)
|
82
|
+
h.merge!(self.details.to_h) if self.details
|
83
|
+
end
|
84
|
+
end
|
85
|
+
alias :to_hash :to_h
|
86
|
+
def to_json(*args)
|
87
|
+
to_h.to_json(*args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
data/lib/cmxl/version.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
describe Cmxl::Fields::AccountBalance do
|
4
|
+
|
5
|
+
context 'Credit' do
|
6
|
+
subject { Cmxl::Fields::AccountBalance.parse( fixture_line(:account_balance_credit) ) }
|
7
|
+
|
8
|
+
it { expect(subject.date).to eql(Date.new(2014,8,29)) }
|
9
|
+
it { expect(subject).to be_credit }
|
10
|
+
it { expect(subject).to_not be_debit }
|
11
|
+
it { expect(subject.currency).to eql('EUR') }
|
12
|
+
|
13
|
+
it { expect(subject.amount).to eql(147.64) }
|
14
|
+
it { expect(subject.amount_in_cents).to eql(14764) }
|
15
|
+
it { expect(subject.sign).to eql(1) }
|
16
|
+
it { expect(subject.to_h).to eql({
|
17
|
+
'date' =>Date.new(2014,8,29),
|
18
|
+
'funds_code' =>"C",
|
19
|
+
'credit' =>true,
|
20
|
+
'debit' =>false,
|
21
|
+
'currency' =>"EUR",
|
22
|
+
'amount' =>147.64,
|
23
|
+
'amount_in_cents' =>14764,
|
24
|
+
'sign' =>1
|
25
|
+
}) }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'Debit' do
|
29
|
+
subject { Cmxl::Fields::AccountBalance.parse( fixture_line(:account_balance_debit) ) }
|
30
|
+
|
31
|
+
it { expect(subject.date).to eql(Date.new(2014,8,29)) }
|
32
|
+
it { expect(subject).to_not be_credit }
|
33
|
+
it { expect(subject).to be_debit }
|
34
|
+
it { expect(subject.currency).to eql('EUR') }
|
35
|
+
|
36
|
+
it { expect(subject.amount).to eql(-147.64) }
|
37
|
+
it { expect(subject.amount_in_cents).to eql(-14764) }
|
38
|
+
it { expect(subject.sign).to eql(-1) }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::AccountIdentification do
|
4
|
+
|
5
|
+
context 'legacy data' do
|
6
|
+
subject { Cmxl::Fields::AccountBalance.parse( fixture_line(:account_identification_legacy) ) }
|
7
|
+
|
8
|
+
it { expect(subject.bank_code).to eql('70011110') }
|
9
|
+
it { expect(subject.account_number).to eql('4005287001') }
|
10
|
+
it { expect(subject.currency).to eql('EUR') }
|
11
|
+
it { expect(subject.country).to be_nil }
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'iban' do
|
16
|
+
subject { Cmxl::Fields::AccountBalance.parse( fixture_line(:account_identification_iban) ) }
|
17
|
+
it { expect(subject.country).to eql('PL') }
|
18
|
+
it { expect(subject.ban).to eql('25106000760000888888888888') }
|
19
|
+
it { expect(subject.iban).to eql('PL25106000760000888888888888') }
|
20
|
+
it { expect(subject.bank_code).to be_nil }
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::AvailableBalance do
|
4
|
+
|
5
|
+
subject { Cmxl::Fields::AvailableBalance.parse(fixture_line(:available_balance)) }
|
6
|
+
|
7
|
+
it { expect(subject.date).to eql(Date.new(2014,9,1)) }
|
8
|
+
it { expect(subject).to_not be_credit }
|
9
|
+
it { expect(subject).to be_debit }
|
10
|
+
it { expect(subject.currency).to eql('EUR') }
|
11
|
+
|
12
|
+
it { expect(subject.amount).to eql(-3.66) }
|
13
|
+
it { expect(subject.amount_in_cents).to eql(-366) }
|
14
|
+
it { expect(subject.sign).to eql(-1) }
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::ClosingBalance do
|
4
|
+
|
5
|
+
subject { Cmxl::Fields::ClosingBalance.parse(fixture_line(:closing_balance)) }
|
6
|
+
|
7
|
+
it { expect(subject.date).to eql(Date.new(2014,9,1)) }
|
8
|
+
it { expect(subject).to be_credit }
|
9
|
+
it { expect(subject).to_not be_debit }
|
10
|
+
|
11
|
+
it { expect(subject.amount).to eql(137.0) }
|
12
|
+
it { expect(subject.amount_in_cents).to eql(13700) }
|
13
|
+
it { expect(subject.sign).to eql(1) }
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::Reference do
|
4
|
+
|
5
|
+
subject { Cmxl::Fields::Reference.parse(fixture_line(:reference)) }
|
6
|
+
|
7
|
+
it { expect(subject.reference).to eql('D140902049') }
|
8
|
+
it { expect(subject.date).to eql(Date.new(2014,9,2)) }
|
9
|
+
it { expect(subject.statement_identifier).to eql('D') }
|
10
|
+
it { expect(subject.additional_number).to eql('049') }
|
11
|
+
it { expect(subject.to_h).to eql({"statement_identifier"=>"D", "date"=> Date.new(2014,9,2), "additional_number"=>"049", "reference"=>"D140902049"}) }
|
12
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::StatementDetails do
|
4
|
+
|
5
|
+
context 'sepa' do
|
6
|
+
subject { Cmxl::Fields::StatementDetails.parse(fixture_line(:statement_details)) }
|
7
|
+
|
8
|
+
it { expect(subject.transaction_code).to eql('171') }
|
9
|
+
it { expect(subject.seperator).to eql('?') }
|
10
|
+
it { expect(subject.description).to eql('SEPA LASTSCHRIFT KUNDE') }
|
11
|
+
it { expect(subject.information).to eql('EREF+TRX-0A4A47C3-F846-4729-8A1B-5DF620FMREF+CAC97D2144174318AC18D9BF815BD4FBCRED+DE98ZZZ09999999999SVWZ+FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F') }
|
12
|
+
it { expect(subject.bic).to eql('HYVEDEMMXXX' ) }
|
13
|
+
it { expect(subject.name).to eql('Peter Pan') }
|
14
|
+
it { expect(subject.iban).to eql('HUkkbbbsssskcccccccccccccccx') }
|
15
|
+
it { expect(subject.sub_fields).to eql({
|
16
|
+
"00" => "SEPA LASTSCHRIFT KUNDE",
|
17
|
+
"10" => "281",
|
18
|
+
"20" => "EREF+TRX-0A4A47C3-F846-4729",
|
19
|
+
"21" => "-8A1B-5DF620F",
|
20
|
+
"22" => "MREF+CAC97D2144174318AC18D9",
|
21
|
+
"23" => "BF815BD4FB",
|
22
|
+
"24" => "CRED+DE98ZZZ09999999999",
|
23
|
+
"25" => "SVWZ+FOO TRX-0A4A47C3-F84",
|
24
|
+
"26" => "6-4729-8A1B-5DF620F",
|
25
|
+
"30" => "HYVEDEMMXXX",
|
26
|
+
"31" => "HUkkbbbsssskcccccccccccccccx",
|
27
|
+
"32" => "Peter Pan",
|
28
|
+
"34" => "171"
|
29
|
+
}) }
|
30
|
+
it { expect(subject.sepa).to eql({
|
31
|
+
"CRED" => "DE98ZZZ09999999999",
|
32
|
+
"EREF" => "TRX-0A4A47C3-F846-4729-8A1B-5DF620F",
|
33
|
+
"MREF" => "CAC97D2144174318AC18D9BF815BD4FB",
|
34
|
+
"SVWZ" => "FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F"
|
35
|
+
}) }
|
36
|
+
|
37
|
+
it { expect(subject.to_h).to eql({'bic'=>"HYVEDEMMXXX", 'iban'=>"HUkkbbbsssskcccccccccccccccx", 'name' => "Peter Pan", 'sepa' => {"EREF"=>"TRX-0A4A47C3-F846-4729-8A1B-5DF620F", "MREF"=>"CAC97D2144174318AC18D9BF815BD4FB", "CRED"=>"DE98ZZZ09999999999", "SVWZ"=>"FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F"}, 'information' => "EREF+TRX-0A4A47C3-F846-4729-8A1B-5DF620FMREF+CAC97D2144174318AC18D9BF815BD4FBCRED+DE98ZZZ09999999999SVWZ+FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F", 'description' => "SEPA LASTSCHRIFT KUNDE", 'sub_fields' => {"00"=>"SEPA LASTSCHRIFT KUNDE", "10"=>"281", "20"=>"EREF+TRX-0A4A47C3-F846-4729", "21"=>"-8A1B-5DF620F", "22"=>"MREF+CAC97D2144174318AC18D9", "23"=>"BF815BD4FB", "24"=>"CRED+DE98ZZZ09999999999", "25"=>"SVWZ+FOO TRX-0A4A47C3-F84", "26"=>"6-4729-8A1B-5DF620F", "30"=>"HYVEDEMMXXX", "31"=>"HUkkbbbsssskcccccccccccccccx", "32"=>"Peter Pan", "34"=>"171"}, 'transaction_code' => "171", 'details' => "?00SEPA LASTSCHRIFT KUNDE?10281?20EREF+TRX-0A4A47C3-F846-4729?21-8A1B-5DF620F?22MREF+CAC97D2144174318AC18D9?23BF815BD4FB?24CRED+DE98ZZZ09999999999?25SVWZ+FOO TRX-0A4A47C3-F84?266-4729-8A1B-5DF620F?30HYVEDEMMXXX?31HUkkbbbsssskcccccccccccccccx?32Peter Pan?34171"}) }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::StatementNumber do
|
4
|
+
|
5
|
+
subject { Cmxl::Fields::StatementNumber.parse(fixture_line(:statement_number)) }
|
6
|
+
|
7
|
+
it { expect(subject.statement_number).to eql('00035') }
|
8
|
+
it { expect(subject.sequence_number).to eql('001') }
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cmxl::Fields::StatementLine do
|
4
|
+
|
5
|
+
subject { Cmxl::Fields::StatementLine.parse(fixture_line(:statement_line)) }
|
6
|
+
|
7
|
+
it { expect(subject.date).to eql(Date.new(2014,9,1)) }
|
8
|
+
it { expect(subject.entry_date).to eql(Date.new(2014,9,2)) }
|
9
|
+
it { expect(subject.funds_code).to eql('D') }
|
10
|
+
it { expect(subject.currency_letter).to eql('R') }
|
11
|
+
it { expect(subject.amount).to eql(-1.62) }
|
12
|
+
it { expect(subject.amount_in_cents).to eql(-162) }
|
13
|
+
it { expect(subject.swift_code).to eql('NTRF') }
|
14
|
+
it { expect(subject.reference).to eql('0000549855700010') }
|
15
|
+
it { expect(subject.bank_reference).to eql('025498557/000001') }
|
16
|
+
it { expect(subject).to_not be_credit }
|
17
|
+
it { expect(subject).to be_debit }
|
18
|
+
it { expect(subject.sign).to eql(-1) }
|
19
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Unknwn fields' do
|
4
|
+
|
5
|
+
subject { Cmxl::Field.parse(':42F:C140908EUR000000000136,02') }
|
6
|
+
it { expect(subject.tag).to eql('42') }
|
7
|
+
it { expect(subject.source).to eql('C140908EUR000000000136,02') }
|
8
|
+
it { expect(subject.to_h).to eql({tag: '42', modifier: 'F', source: 'C140908EUR000000000136,02'}) }
|
9
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
:60F:C140829EUR000000000147,64
|