sepa_file_parser 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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sepa_file_parser/camt052/base.rb +18 -0
  3. data/lib/sepa_file_parser/camt052/report.rb +73 -0
  4. data/lib/sepa_file_parser/camt053/base.rb +18 -0
  5. data/lib/sepa_file_parser/camt053/statement.rb +75 -0
  6. data/lib/sepa_file_parser/camt054/base.rb +18 -0
  7. data/lib/sepa_file_parser/camt054/notification.rb +54 -0
  8. data/lib/sepa_file_parser/errors.rb +12 -0
  9. data/lib/sepa_file_parser/file.rb +10 -0
  10. data/lib/sepa_file_parser/general/account.rb +66 -0
  11. data/lib/sepa_file_parser/general/account_balance.rb +61 -0
  12. data/lib/sepa_file_parser/general/batch_detail.rb +24 -0
  13. data/lib/sepa_file_parser/general/charges.rb +25 -0
  14. data/lib/sepa_file_parser/general/creditor.rb +46 -0
  15. data/lib/sepa_file_parser/general/debitor.rb +46 -0
  16. data/lib/sepa_file_parser/general/entry.rb +117 -0
  17. data/lib/sepa_file_parser/general/group_header.rb +38 -0
  18. data/lib/sepa_file_parser/general/pain_entry.rb +46 -0
  19. data/lib/sepa_file_parser/general/postal_address.rb +45 -0
  20. data/lib/sepa_file_parser/general/record.rb +49 -0
  21. data/lib/sepa_file_parser/general/transaction.rb +139 -0
  22. data/lib/sepa_file_parser/general/type/builder.rb +20 -0
  23. data/lib/sepa_file_parser/general/type/code.rb +13 -0
  24. data/lib/sepa_file_parser/general/type/proprietary.rb +13 -0
  25. data/lib/sepa_file_parser/misc.rb +28 -0
  26. data/lib/sepa_file_parser/pain001/base.rb +18 -0
  27. data/lib/sepa_file_parser/pain001/payment_information.rb +44 -0
  28. data/lib/sepa_file_parser/pain008/base.rb +18 -0
  29. data/lib/sepa_file_parser/pain008/payment_information.rb +44 -0
  30. data/lib/sepa_file_parser/register.rb +23 -0
  31. data/lib/sepa_file_parser/string.rb +10 -0
  32. data/lib/sepa_file_parser/version.rb +5 -0
  33. data/lib/sepa_file_parser/xml.rb +45 -0
  34. data/lib/sepa_file_parser.rb +38 -0
  35. metadata +160 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module SepaFileParser
6
+ class GroupHeader
7
+
8
+ attr_reader :message_id,
9
+ :creation_date_time,
10
+ :additional_information,
11
+ :message_pagination,
12
+ :xml_data
13
+
14
+ def initialize(xml_data)
15
+ @xml_data = xml_data
16
+ @message_id = xml_data.xpath('MsgId/text()').text
17
+ @creation_date_time = Time.parse(xml_data.xpath('CreDtTm/text()').text)
18
+ @message_pagination = (x = xml_data.xpath('MsgPgntn')).empty? ? nil : MessagePagination.new(x)
19
+ @additional_information = xml_data.xpath('AddtlInf/text()').text
20
+ end
21
+ end
22
+
23
+ class MessagePagination
24
+
25
+ attr_reader :page_number,
26
+ :xml_data
27
+
28
+ def initialize(xml_data)
29
+ @xml_data = xml_data
30
+ @page_number = xml_data.xpath('PgNb/text()').text.to_i
31
+ @last_page_indicator = xml_data.xpath('LastPgInd/text()').text == 'true'
32
+ end
33
+
34
+ def last_page?
35
+ @last_page_indicator
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class PainEntry
5
+
6
+ attr_reader :xml_data
7
+
8
+ def initialize(xml_data)
9
+ @xml_data = xml_data
10
+
11
+ @amount = [
12
+ xml_data.xpath('Amt/InstdAmt').first,
13
+ xml_data.xpath('InstdAmt').first,
14
+ ].find { |a| a != nil }.text
15
+ end
16
+
17
+ def amount
18
+ SepaFileParser::Misc.to_amount(@amount)
19
+ end
20
+
21
+ def amount_in_cents
22
+ SepaFileParser::Misc.to_amount_in_cents(@amount)
23
+ end
24
+
25
+ # @return [String]
26
+ def currency
27
+ @currency ||= [
28
+ xml_data.xpath('Amt/InstdAmt/@Ccy').first,
29
+ xml_data.xpath('InstdAmt/@Ccy').first,
30
+ ].find { |a| a != nil }.text
31
+ end
32
+
33
+ def account
34
+ @account ||= SepaFileParser::Account.from_pain_data(
35
+ xml_data,
36
+ currency,
37
+ 'Cdtr',
38
+ )
39
+ end
40
+
41
+ def remittance_information
42
+ @remittance_information ||= xml_data.xpath('RmtInf/Ustrd').text
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class PostalAddress
5
+
6
+ attr_reader :xml_data
7
+
8
+ def initialize(xml_data)
9
+ @xml_data = xml_data
10
+ end
11
+
12
+ # @return [Array<String>]
13
+ def lines # May be empty
14
+ xml_data.xpath('AdrLine').map do |x|
15
+ x.xpath('text()').text
16
+ end
17
+ end
18
+
19
+ # @return [String]
20
+ def street_name # May be missing
21
+ xml_data.xpath('StrtNm/text()').text
22
+ end
23
+
24
+ # @return [String]
25
+ def building_number # May be missing
26
+ xml_data.xpath('BldgNb/text()').text
27
+ end
28
+
29
+ # @return [String]
30
+ def postal_code # May be missing
31
+ xml_data.xpath('PstCd/text()').text
32
+ end
33
+
34
+ # @return [String]
35
+ def town_name # May be missing
36
+ xml_data.xpath('TwnNm/text()').text
37
+ end
38
+
39
+ # @return [String]
40
+ def country # May be missing
41
+ xml_data.xpath('Ctry/text()').text
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class Record
5
+
6
+ attr_reader :xml_data
7
+
8
+ def initialize(xml_data)
9
+ @xml_data = xml_data
10
+ @amount = xml_data.xpath('Amt/text()').text
11
+ end
12
+
13
+ def amount
14
+ SepaFileParser::Misc.to_amount(@amount)
15
+ end
16
+
17
+ def amount_in_cents
18
+ SepaFileParser::Misc.to_amount_in_cents(@amount)
19
+ end
20
+
21
+ def currency
22
+ @currency ||= xml_data.xpath('Amt/@Ccy').text
23
+ end
24
+
25
+ def type
26
+ @type ||= SepaFileParser::Type::Builder.build_type(xml_data.xpath('Tp'))
27
+ end
28
+
29
+ def charges_included?
30
+ @charges_included ||= xml_data.xpath('ChrgInclInd/text()').text.downcase == 'true'
31
+ end
32
+
33
+ def debit
34
+ @debit ||= xml_data.xpath('CdtDbtInd/text()').text.upcase == 'DBIT'
35
+ end
36
+
37
+ def credit?
38
+ !debit
39
+ end
40
+
41
+ def debit?
42
+ debit
43
+ end
44
+
45
+ def sign
46
+ credit? ? 1 : -1
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class Transaction
5
+
6
+ attr_reader :xml_data
7
+
8
+ def initialize(xml_data, debit, amount = nil, currency = nil)
9
+ @xml_data = xml_data
10
+ @debit = debit
11
+ @amount = parse_amount || amount
12
+ @currency = parse_currency || currency
13
+ end
14
+
15
+ def amount
16
+ SepaFileParser::Misc.to_amount(@amount)
17
+ end
18
+
19
+ def amount_in_cents
20
+ SepaFileParser::Misc.to_amount_in_cents(@amount)
21
+ end
22
+
23
+ def currency
24
+ @currency
25
+ end
26
+
27
+ def creditor
28
+ @creditor ||= SepaFileParser::Creditor.new(xml_data)
29
+ end
30
+
31
+ def debitor
32
+ @debitor ||= SepaFileParser::Debitor.new(xml_data)
33
+ end
34
+
35
+ def name
36
+ credit? ? debitor.name : creditor.name
37
+ end
38
+
39
+ def iban
40
+ credit? ? debitor.iban : creditor.iban
41
+ end
42
+
43
+ def bic
44
+ credit? ? debitor.bic : creditor.bic
45
+ end
46
+
47
+ def postal_address
48
+ credit? ? debitor.postal_address : creditor.postal_address
49
+ end
50
+
51
+ def credit?
52
+ !debit
53
+ end
54
+
55
+ def debit?
56
+ debit
57
+ end
58
+
59
+ def debit
60
+ @debit
61
+ end
62
+
63
+ def sign
64
+ credit? ? 1 : -1
65
+ end
66
+
67
+ def remittance_information
68
+ @remittance_information ||= begin
69
+ if (x = xml_data.xpath('RmtInf/Ustrd')).empty?
70
+ nil
71
+ else
72
+ x.collect(&:content).join(' ')
73
+ end
74
+ end
75
+ end
76
+
77
+ def swift_code
78
+ @swift_code ||= xml_data.xpath('BkTxCd/Prtry/Cd/text()').text.split('+')[0]
79
+ end
80
+
81
+ def reference
82
+ @reference ||= xml_data.xpath('Refs/InstrId/text()').text
83
+ end
84
+
85
+ def bank_reference # May be missing
86
+ @bank_reference ||= xml_data.xpath('Refs/AcctSvcrRef/text()').text
87
+ end
88
+
89
+ def end_to_end_reference # May be missing
90
+ @end_to_end_reference ||= xml_data.xpath('Refs/EndToEndId/text()').text
91
+ end
92
+
93
+ def mandate_reference # May be missing
94
+ @mandate_reference ||= xml_data.xpath('Refs/MndtId/text()').text
95
+ end
96
+
97
+ def creditor_reference # May be missing
98
+ @creditor_reference ||= xml_data.xpath('RmtInf/Strd/CdtrRefInf/Ref/text()').text
99
+ end
100
+
101
+ def transaction_id # May be missing
102
+ @transaction_id ||= xml_data.xpath('Refs/TxId/text()').text
103
+ end
104
+
105
+ def creditor_identifier # May be missing
106
+ @creditor_identifier ||= xml_data.xpath('RltdPties/Cdtr/Id/PrvtId/Othr/Id/text()').text
107
+ end
108
+
109
+ def payment_information # May be missing
110
+ @payment_information ||= xml_data.xpath('Refs/PmtInfId/text()').text
111
+ end
112
+
113
+ def additional_information # May be missing
114
+ @addition_information ||= xml_data.xpath('AddtlTxInf/text()').text
115
+ end
116
+
117
+ def reason_code # May be missing
118
+ @reason_code ||= xml_data.xpath('RtrInf/Rsn/Cd/text()').text
119
+ end
120
+
121
+ private
122
+
123
+ def parse_amount
124
+ if xml_data.xpath('Amt').any?
125
+ xml_data.xpath('Amt/text()').text
126
+ elsif xml_data.xpath('AmtDtls').any?
127
+ xml_data.xpath('AmtDtls//Amt/text()').first.text
128
+ end
129
+ end
130
+
131
+ def parse_currency
132
+ if xml_data.xpath('Amt').any?
133
+ xml_data.xpath('Amt/@Ccy').text
134
+ elsif xml_data.xpath('AmtDtls').any?
135
+ xml_data.xpath('AmtDtls//Amt/@Ccy').first.text
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Type
5
+ class Builder
6
+ def self.build_type(xml_data)
7
+ if xml_data.xpath('Prtry').any?
8
+ Proprietary.new(
9
+ xml_data.xpath('Prtry/Id/text()').text,
10
+ xml_data.xpath('Prtry/Issr/text()').text
11
+ )
12
+ elsif xml_data.xpath('Cd').any?
13
+ Code.new(xml_data.xpath('Cd/text()').text)
14
+ else
15
+ nil
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Type
5
+ class Code
6
+ attr_reader :code
7
+
8
+ def initialize(code)
9
+ @code = code
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Type
5
+ class Proprietary
6
+ attr_reader :id, :issuer
7
+
8
+ def initialize(id, issuer)
9
+ @id, @issuer = id, issuer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class Misc
5
+ class << self
6
+
7
+ # @param value [nil, String]
8
+ # @return [Integer, nil]
9
+ def to_amount_in_cents(value)
10
+ return nil if value == nil || value.strip == ''
11
+
12
+ # Using dollars and cents as representation for parts before and after the decimal separator
13
+ dollars, cents = value.split(/,|\./)
14
+ cents ||= '0'
15
+ format('%s%s', dollars, cents.ljust(2, '0')).to_i
16
+ end
17
+
18
+ # @param value [nil, String]
19
+ # @return [BigDecimal, nil]
20
+ def to_amount(value)
21
+ return nil if value == nil || value.strip == ''
22
+
23
+ BigDecimal(value.tr(',', '.'))
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Pain001
5
+ class Base
6
+ attr_reader :group_header, :payment_informations, :xml_data
7
+
8
+ def initialize(xml_data)
9
+ @xml_data = xml_data
10
+ # CstmrCdtTrfInitn = Customer Credit Transaction Initiation
11
+ grphdr = xml_data.xpath('CstmrCdtTrfInitn/GrpHdr')
12
+ @group_header = SepaFileParser::GroupHeader.new(grphdr)
13
+ payment_informations = xml_data.xpath('CstmrCdtTrfInitn/PmtInf')
14
+ @payment_informations = payment_informations.map{ |x| PaymentInformation.new(x) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Pain001
5
+ class PaymentInformation
6
+
7
+ attr_reader :xml_data
8
+
9
+ def initialize(xml_data)
10
+ @xml_data = xml_data
11
+ end
12
+
13
+ def identification
14
+ @identification ||= xml_data.xpath('PmtInfId/text()').text
15
+ end
16
+
17
+ def requested_execution_date
18
+ @requested_execution_date ||= Time.parse(xml_data.xpath('ReqdExctnDt/text()').text)
19
+ end
20
+
21
+ def account
22
+ @account ||= SepaFileParser::Account.from_pain_data(
23
+ xml_data,
24
+ entries.first.currency,
25
+ 'Dbtr',
26
+ )
27
+ end
28
+
29
+ def entries
30
+ @entries ||= xml_data.xpath('CdtTrfTxInf').map do |x|
31
+ SepaFileParser::PainEntry.new(x)
32
+ end
33
+ end
34
+
35
+ def source
36
+ xml_data.to_s
37
+ end
38
+
39
+ def self.parse(xml)
40
+ self.new(Nokogiri::XML(xml).xpath('PmtInf'))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Pain008
5
+ class Base
6
+ attr_reader :group_header, :payment_informations, :xml_data
7
+
8
+ def initialize(xml_data)
9
+ @xml_data = xml_data
10
+ # CstmrDrctDbtInitn = Customer Direct Debig Initiation
11
+ grphdr = xml_data.xpath('CstmrDrctDbtInitn/GrpHdr')
12
+ @group_header = SepaFileParser::GroupHeader.new(grphdr)
13
+ payment_informations = xml_data.xpath('CstmrDrctDbtInitn/PmtInf')
14
+ @payment_informations = payment_informations.map{ |x| PaymentInformation.new(x) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ module Pain008
5
+ class PaymentInformation
6
+
7
+ attr_reader :xml_data
8
+
9
+ def initialize(xml_data)
10
+ @xml_data = xml_data
11
+ end
12
+
13
+ def identification
14
+ @identification ||= xml_data.xpath('PmtInfId/text()').text
15
+ end
16
+
17
+ def requested_collection_date
18
+ @requested_collection_date ||= Time.parse(xml_data.xpath('ReqdColltnDt/text()').text)
19
+ end
20
+
21
+ def account
22
+ @account ||= SepaFileParser::Account.from_pain_data(
23
+ xml_data,
24
+ entries.first.currency,
25
+ 'Cdtr',
26
+ )
27
+ end
28
+
29
+ def entries
30
+ @entries ||= xml_data.xpath('DrctDbtTxInf').map do |x|
31
+ SepaFileParser::PainEntry.new(x)
32
+ end
33
+ end
34
+
35
+ def source
36
+ xml_data.to_s
37
+ end
38
+
39
+ def self.parse(xml)
40
+ self.new(Nokogiri::XML(xml).xpath('PmtInf'))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './xml'
4
+
5
+ # Add registrations
6
+ ## CAMT052
7
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.052.001.02', :camt052)
8
+
9
+ ## CAMT053
10
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.053.001.02', :camt053)
11
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.053.001.04', :camt053)
12
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.053.001.08', :camt053)
13
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.053.001.10', :camt053)
14
+
15
+ ## CAMT054
16
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.054.001.02', :camt054)
17
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.054.001.04', :camt054)
18
+
19
+ ## PAIN001
20
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03', :pain001)
21
+
22
+ ## PAIN008
23
+ SepaFileParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:pain.008.003.02', :pain008)
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class String
5
+ def self.parse(raw_camt)
6
+ doc = Nokogiri::XML raw_camt
7
+ SepaFileParser::Xml.parse(doc)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ VERSION = '0.1.0'.freeze
5
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SepaFileParser
4
+ class Xml
5
+ PARSER_MAPPING = {
6
+ camt052: SepaFileParser::Camt052::Base,
7
+ camt053: SepaFileParser::Camt053::Base,
8
+ camt054: SepaFileParser::Camt054::Base,
9
+ pain001: SepaFileParser::Pain001::Base,
10
+ pain008: SepaFileParser::Pain008::Base,
11
+ }.freeze
12
+
13
+ SUPPORTED_PARSERS = PARSER_MAPPING.keys.freeze
14
+
15
+ @namespace_parsers = {}
16
+
17
+ def self.register(namespace, parser)
18
+ if !SUPPORTED_PARSERS.include?(parser)
19
+ raise SepaFileParser::Errors::UnsupportedParserClass, parser.to_s
20
+ end
21
+
22
+ if @namespace_parsers.key?(namespace) # Prevent overwriting existing registrations
23
+ raise SepaFileParser::Errors::NamespaceAlreadyRegistered, namespace
24
+ end
25
+
26
+ @namespace_parsers[namespace] = PARSER_MAPPING[parser]
27
+ end
28
+
29
+ def self.parse(doc)
30
+ if !doc.is_a?(Nokogiri::XML::Document)
31
+ raise SepaFileParser::Errors::NotXMLError, doc.class
32
+ end
33
+
34
+ namespace = doc.namespaces['xmlns']
35
+ doc.remove_namespaces!
36
+
37
+ parser_class = @namespace_parsers[namespace]
38
+ if parser_class == nil
39
+ raise SepaFileParser::Errors::UnsupportedNamespaceError, namespace
40
+ end
41
+
42
+ return parser_class.new(doc.xpath('Document'))
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'time'
5
+ require 'bigdecimal'
6
+
7
+ require_relative 'sepa_file_parser/misc'
8
+ require_relative 'sepa_file_parser/version'
9
+ require_relative 'sepa_file_parser/errors'
10
+ require_relative 'sepa_file_parser/general/account_balance'
11
+ require_relative 'sepa_file_parser/general/creditor'
12
+ require_relative 'sepa_file_parser/general/debitor'
13
+ require_relative 'sepa_file_parser/general/entry'
14
+ require_relative 'sepa_file_parser/general/record'
15
+ require_relative 'sepa_file_parser/general/charges'
16
+ require_relative 'sepa_file_parser/general/account'
17
+ require_relative 'sepa_file_parser/general/batch_detail'
18
+ require_relative 'sepa_file_parser/general/group_header'
19
+ require_relative 'sepa_file_parser/general/pain_entry'
20
+ require_relative 'sepa_file_parser/general/postal_address'
21
+ require_relative 'sepa_file_parser/general/transaction'
22
+ require_relative 'sepa_file_parser/general/type/builder'
23
+ require_relative 'sepa_file_parser/general/type/code'
24
+ require_relative 'sepa_file_parser/general/type/proprietary'
25
+ require_relative 'sepa_file_parser/camt052/report'
26
+ require_relative 'sepa_file_parser/camt052/base'
27
+ require_relative 'sepa_file_parser/camt053/statement'
28
+ require_relative 'sepa_file_parser/camt053/base'
29
+ require_relative 'sepa_file_parser/camt054/notification'
30
+ require_relative 'sepa_file_parser/camt054/base'
31
+ require_relative 'sepa_file_parser/pain001/payment_information'
32
+ require_relative 'sepa_file_parser/pain001/base'
33
+ require_relative 'sepa_file_parser/pain008/payment_information'
34
+ require_relative 'sepa_file_parser/pain008/base'
35
+ require_relative 'sepa_file_parser/file'
36
+ require_relative 'sepa_file_parser/string'
37
+ require_relative 'sepa_file_parser/register'
38
+ require_relative 'sepa_file_parser/xml'