ofx_kit 1.0.2 → 1.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.
- checksums.yaml +4 -4
- data/README.md +23 -54
- data/lib/generators/ofx_kit/install_generator.rb +23 -0
- data/lib/generators/ofx_kit/templates/ofx_kit.rb +40 -0
- data/lib/ofx_kit/bank_statement.rb +8 -0
- data/lib/ofx_kit/configuration/core.rb +19 -80
- data/lib/ofx_kit/configuration/mapping_applicator.rb +1 -1
- data/lib/ofx_kit/configuration/section_proxy.rb +8 -2
- data/lib/ofx_kit/credit_card_account.rb +8 -0
- data/lib/ofx_kit/credit_card_statement.rb +8 -0
- data/lib/ofx_kit/errors/error.rb +2 -7
- data/lib/ofx_kit/errors/invalid_body.rb +8 -0
- data/lib/ofx_kit/errors/invalid_configuration.rb +8 -0
- data/lib/ofx_kit/errors/invalid_header.rb +8 -0
- data/lib/ofx_kit/errors/multiple_statements.rb +9 -0
- data/lib/ofx_kit/errors/parse.rb +8 -0
- data/lib/ofx_kit/errors/unsupported_encoding.rb +8 -0
- data/lib/ofx_kit/errors/unsupported_version.rb +15 -0
- data/lib/ofx_kit/errors.rb +10 -0
- data/lib/ofx_kit/parser.rb +4 -4
- data/lib/ofx_kit/tokenizer/ofx1.rb +2 -2
- data/lib/ofx_kit/tokenizer/ofx2.rb +1 -1
- data/lib/ofx_kit/version.rb +1 -1
- data/lib/ofx_kit.rb +4 -11
- metadata +12 -24
- data/lib/generators/ofx_kit/eject_generator.rb +0 -27
- data/lib/generators/ofx_kit/templates/ofx_mappings.yml +0 -33
- data/lib/ofx_kit/errors/configuration_error.rb +0 -8
- data/lib/ofx_kit/errors/encoding_error.rb +0 -8
- data/lib/ofx_kit/errors/invalid_body_error.rb +0 -8
- data/lib/ofx_kit/errors/invalid_header_error.rb +0 -8
- data/lib/ofx_kit/errors/multiple_statements_error.rb +0 -8
- data/lib/ofx_kit/errors/parse_error.rb +0 -8
- data/lib/ofx_kit/errors/unsupported_version_error.rb +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 131031f6d262d168634bd74678d9c7c8aa2820a1ef87c6cd1077b6d06106404d
|
|
4
|
+
data.tar.gz: b5018a6676ec15d0f28f5640d1fdc5048efacc92f350be689ee53264e230027a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e548553b82237b1c56e193dc85f3bb5463abdf2d0082074b1731cb73dfdf435c1677e410aa9ad1a1e8a1bc37d68fe0d128c633948733e6d7fd72e3400edb97d
|
|
7
|
+
data.tar.gz: c37a45193aee74dedb292442965de84e79223eb672496b29cd079f09bfbdaadafefadd71d9ffbc116b089cf28c1b2706b6183b29fef746f3ca862cc1f322a255
|
data/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# OFX Kit
|
|
2
2
|
|
|
3
|
-
[](https://github.com/lucasgeron/ofx_kit)
|
|
4
|
+
[](https://rubygems.org/gems/ofx_kit)
|
|
5
|
+
[](https://rubygems.org/gems/ofx_kit)
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
A Ruby gem for parsing OFX (Open Financial Exchange) files. Supports OFX 1.x (SGML) and OFX 2.x (XML), bank statements and credit card statements, with a fluent API and configurable field mappings.
|
|
@@ -10,7 +12,7 @@ A Ruby gem for parsing OFX (Open Financial Exchange) files. Supports OFX 1.x (SG
|
|
|
10
12
|
Add to your Gemfile:
|
|
11
13
|
|
|
12
14
|
```ruby
|
|
13
|
-
gem 'ofx_kit', '~> 1.0'
|
|
15
|
+
gem 'ofx_kit', '~> 1.0', '>= 1.0.2'
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
## Usage
|
|
@@ -126,18 +128,28 @@ ofx.balance # => OFX::MultipleStatementsError (use `balances`)
|
|
|
126
128
|
|
|
127
129
|
## Configuration
|
|
128
130
|
|
|
131
|
+
### Rails setup
|
|
132
|
+
|
|
133
|
+
Generate a pre-filled initializer with all options commented out:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
rails generate ofx_kit:install
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This creates `config/initializers/ofx_kit.rb`. Uncomment and adjust whatever you need — everything else stays at its default.
|
|
140
|
+
|
|
129
141
|
### Field mappings
|
|
130
142
|
|
|
131
|
-
Use `map` to add
|
|
143
|
+
Use `map` inside an `OFX.configure` block to add custom attributes or rename built-in ones:
|
|
132
144
|
|
|
133
145
|
```ruby
|
|
134
146
|
OFX.configure do |config|
|
|
135
|
-
#
|
|
147
|
+
# Add a bank-specific tag the gem doesn't know about by default
|
|
136
148
|
config.bank_account.map "AGENCIA", to: "branch_code"
|
|
137
149
|
config.transaction.map "HISPAYEEMEMO", to: "extended_memo"
|
|
138
150
|
|
|
139
|
-
# Rename a built-in field to
|
|
140
|
-
config.transaction.map "FITID", to: "uid"
|
|
151
|
+
# Rename a built-in field to match your domain
|
|
152
|
+
config.transaction.map "FITID", to: "uid" # default is fit_id
|
|
141
153
|
config.transaction.map "NAME", to: "payee_name"
|
|
142
154
|
end
|
|
143
155
|
|
|
@@ -149,64 +161,21 @@ ofx.transactions.first.uid # => "20240115001"
|
|
|
149
161
|
```
|
|
150
162
|
|
|
151
163
|
> **Protected core fields** — `CURDEF`, `TRNAMT`, `DTPOSTED`, `DTUSER`, `BALAMT`, `DTASOF`
|
|
152
|
-
> are used internally to build Money objects and parse dates. They cannot be
|
|
164
|
+
> are used internally to build Money objects and parse dates. They cannot be remapped;
|
|
153
165
|
> attempting to do so raises `OFX::ConfigurationError`.
|
|
154
166
|
|
|
155
|
-
### Loading mappings from a YAML file
|
|
156
|
-
|
|
157
|
-
For larger configurations or Rails apps, a YAML file is cleaner than inline `map` calls.
|
|
158
|
-
|
|
159
|
-
**Rails** — eject the template with:
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
rails generate ofx:eject
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
This creates `config/initializers/ofx_mappings.yml`, which the gem detects and loads
|
|
166
|
-
automatically on boot — no `OFX.configure` call needed.
|
|
167
|
-
|
|
168
|
-
**Standalone** — point `load_mappings` at any YAML file:
|
|
169
|
-
|
|
170
|
-
```ruby
|
|
171
|
-
OFX.configure do |config|
|
|
172
|
-
config.load_mappings("config/ofx_mappings.yml")
|
|
173
|
-
end
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
The file must have a `FIELDS:` top-level key:
|
|
177
|
-
|
|
178
|
-
```yaml
|
|
179
|
-
FIELDS:
|
|
180
|
-
STMTTRN:
|
|
181
|
-
HISPAYEEMEMO: extended_memo # → transaction.extended_memo (new field)
|
|
182
|
-
FITID: uid # → transaction.uid (default was fit_id)
|
|
183
|
-
BANKACCTFROM:
|
|
184
|
-
AGENCIA: branch_code # → account.branch_code (new field)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
167
|
### Silencing warnings
|
|
188
168
|
|
|
189
|
-
`transactions` and `balances`
|
|
169
|
+
`transactions` and `balances` emit a warning when aggregating across multiple statements in the same file. To silence them:
|
|
190
170
|
|
|
191
171
|
```ruby
|
|
192
|
-
OFX.config.multi_statement_warnings = false
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Currency
|
|
196
|
-
|
|
197
|
-
The OFX specification requires `CURDEF` in every statement (`STMTRS` / `CCSTMTRS`). If the tag is absent, the gem raises `OFX::Errors::InvalidBodyError` rather than silently assuming a currency.
|
|
198
|
-
|
|
199
|
-
### Rails
|
|
200
|
-
|
|
201
|
-
**Behavioral options** — create a standard initializer:
|
|
202
|
-
|
|
203
|
-
```ruby
|
|
204
|
-
# config/initializers/ofx.rb
|
|
205
172
|
OFX.configure do |config|
|
|
206
|
-
config.multi_statement_warnings = false
|
|
173
|
+
config.multi_statement_warnings = false
|
|
207
174
|
end
|
|
208
175
|
```
|
|
209
176
|
|
|
177
|
+
This option is already included (commented out) in the initializer generated by `ofx_kit:install`.
|
|
178
|
+
|
|
210
179
|
## Contributing
|
|
211
180
|
|
|
212
181
|
1. Fork the repository and create a feature branch.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module OfxKit # :nodoc:
|
|
6
|
+
module Generators # :nodoc:
|
|
7
|
+
# Generates an OFX Kit initializer with all configuration options
|
|
8
|
+
# pre-written and commented, ready to uncomment and customize.
|
|
9
|
+
#
|
|
10
|
+
# === Example
|
|
11
|
+
#
|
|
12
|
+
# rails generate ofx_kit:install
|
|
13
|
+
class InstallGenerator < Rails::Generators::Base
|
|
14
|
+
source_root File.expand_path('templates', __dir__)
|
|
15
|
+
|
|
16
|
+
desc 'Creates an OFX Kit initializer at config/initializers/ofx_kit.rb'
|
|
17
|
+
|
|
18
|
+
def copy_initializer
|
|
19
|
+
copy_file 'ofx_kit.rb', 'config/initializers/ofx_kit.rb'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# config/initializers/ofx_kit.rb
|
|
2
|
+
|
|
3
|
+
OFX.configure do |config|
|
|
4
|
+
# ---------------------------------------------------------------------------
|
|
5
|
+
# Field Mappings
|
|
6
|
+
# ---------------------------------------------------------------------------
|
|
7
|
+
# Map OFX XML tags to Ruby attribute names on domain objects.
|
|
8
|
+
# Uncomment any line to override its default, or add new lines for custom tags.
|
|
9
|
+
#
|
|
10
|
+
# Protected core fields cannot be remapped (raises OFX::Error::InvalidConfiguration):
|
|
11
|
+
# CURDEF, TRNAMT, DTPOSTED, DTUSER, BALAMT, DTASOF
|
|
12
|
+
#
|
|
13
|
+
# Default transaction fields (STMTTRN):
|
|
14
|
+
# config.transaction.map "FITID", to: "fit_id"
|
|
15
|
+
# config.transaction.map "TRNTYPE", to: "type"
|
|
16
|
+
# config.transaction.map "NAME", to: "name"
|
|
17
|
+
# config.transaction.map "MEMO", to: "memo"
|
|
18
|
+
# config.transaction.map "PAYEE", to: "payee"
|
|
19
|
+
# config.transaction.map "CHECKNUM", to: "check_number"
|
|
20
|
+
# config.transaction.map "REFNUM", to: "ref_number"
|
|
21
|
+
# config.transaction.map "SIC", to: "sic"
|
|
22
|
+
# config.transaction.map "HISPAYEEMEMO", to: "extended_memo" # custom field example
|
|
23
|
+
#
|
|
24
|
+
# Default bank account fields (BANKACCTFROM):
|
|
25
|
+
# config.bank_account.map "BANKID", to: "bank_id"
|
|
26
|
+
# config.bank_account.map "ACCTID", to: "account_id"
|
|
27
|
+
# config.bank_account.map "ACCTTYPE", to: "account_type"
|
|
28
|
+
# config.bank_account.map "BRANCHID", to: "branch_id"
|
|
29
|
+
# config.bank_account.map "AGENCIA", to: "branch_code" # custom field example
|
|
30
|
+
#
|
|
31
|
+
# Default credit card account fields (CCACCTFROM):
|
|
32
|
+
# config.credit_card_account.map "ACCTID", to: "account_id"
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Behavioral Options
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# `transactions` and `balances` emit a warning when aggregating across
|
|
38
|
+
# multiple statements in the same file. Silence them:
|
|
39
|
+
# config.multi_statement_warnings = false
|
|
40
|
+
end
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
module OFX
|
|
4
4
|
# Represents a complete bank statement parsed from an OFX file,
|
|
5
5
|
# aggregating the account, its transactions, and the closing balance.
|
|
6
|
+
#
|
|
7
|
+
# === Example
|
|
8
|
+
#
|
|
9
|
+
# stmt = OFX.new("bank.ofx").statements.first
|
|
10
|
+
# stmt.account #=> #<OFX::BankAccount ...>
|
|
11
|
+
# stmt.balance #=> #<OFX::Balance ...>
|
|
12
|
+
# stmt.transactions #=> #<OFX::TransactionCollection ...>
|
|
13
|
+
# stmt.bank_statement? #=> true
|
|
6
14
|
class BankStatement < Base::Statement
|
|
7
15
|
# Always +true+.
|
|
8
16
|
def bank_statement? = true
|
|
@@ -6,23 +6,20 @@ module OFX
|
|
|
6
6
|
##
|
|
7
7
|
# Manages XML-to-Ruby field mappings used during OFX document parsing.
|
|
8
8
|
#
|
|
9
|
-
# Mappings are
|
|
10
|
-
# - *Core* (+core_mappings.yml+): OFX-standard fields
|
|
11
|
-
#
|
|
12
|
-
# - *
|
|
13
|
-
#
|
|
9
|
+
# Mappings are resolved in three layers (last wins):
|
|
10
|
+
# - *Core* (+core_mappings.yml+): OFX-standard fields referenced by name inside
|
|
11
|
+
# Base::Builder. These cannot be overridden.
|
|
12
|
+
# - *Default* (+field_mappings.yml+): built-in convenience mappings (e.g. FITID → fit_id).
|
|
13
|
+
# They can be renamed via the OFX.configure block.
|
|
14
|
+
# - *User*: explicit mappings added at runtime via the OFX.configure block.
|
|
14
15
|
class Configuration
|
|
15
16
|
##
|
|
16
17
|
# Absolute path to the built-in core OFX field mappings (read-only).
|
|
17
18
|
CORE_MAPPINGS_PATH = File.join(__dir__, '..', 'mappings', 'core_mappings.yml')
|
|
18
|
-
##
|
|
19
|
-
# Absolute path to the built-in user-layer field mappings.
|
|
20
|
-
MAPPINGS_PATH = File.join(__dir__, '..', 'mappings', 'field_mappings.yml')
|
|
21
19
|
|
|
22
20
|
##
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
RAILS_MAPPINGS_PATH = 'config/initializers/ofx_mappings.yml'
|
|
21
|
+
# Absolute path to the built-in default field mappings.
|
|
22
|
+
MAPPINGS_PATH = File.join(__dir__, '..', 'mappings', 'field_mappings.yml')
|
|
26
23
|
|
|
27
24
|
##
|
|
28
25
|
# Controls whether a warning is emitted when OFX::Parser#transactions or
|
|
@@ -37,21 +34,19 @@ module OFX
|
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
##
|
|
40
|
-
# Creates a new Configuration instance.
|
|
41
|
-
|
|
42
|
-
# initialization. Defaults to RAILS_MAPPINGS_PATH expanded from the working directory.
|
|
43
|
-
def initialize(auto_load_path: File.expand_path(RAILS_MAPPINGS_PATH))
|
|
37
|
+
# Creates a new Configuration instance with the built-in field mappings loaded.
|
|
38
|
+
def initialize
|
|
44
39
|
@multi_statement_warnings = true
|
|
45
40
|
|
|
46
41
|
core = YAML.safe_load_file(CORE_MAPPINGS_PATH)
|
|
47
|
-
@sections
|
|
48
|
-
@core_fields
|
|
42
|
+
@sections = core.fetch('SECTIONS', {})
|
|
43
|
+
@core_fields = core.fetch('FIELDS', {})
|
|
49
44
|
@section_to_tag = @sections.invert
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
@
|
|
46
|
+
defaults = YAML.safe_load_file(MAPPINGS_PATH)
|
|
47
|
+
@default_fields = defaults.fetch('FIELDS', {})
|
|
53
48
|
|
|
54
|
-
|
|
49
|
+
@user_fields = {}
|
|
55
50
|
end
|
|
56
51
|
|
|
57
52
|
##
|
|
@@ -88,70 +83,14 @@ module OFX
|
|
|
88
83
|
##
|
|
89
84
|
# Returns the merged Hash of XML tag to Ruby attribute mappings for the given
|
|
90
85
|
# +section_name+ (String or Symbol).
|
|
91
|
-
#
|
|
86
|
+
# Resolution order: core → default → user (user wins).
|
|
92
87
|
def xml_mappings_for(section_name)
|
|
93
88
|
tag = xml_tag_for(section_name)
|
|
94
89
|
return {} unless tag
|
|
95
90
|
|
|
96
|
-
(@core_fields[tag] || {})
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
##
|
|
100
|
-
# Merges additional field mappings from a YAML file at +path+ (String)
|
|
101
|
-
# into the user-layer configuration.
|
|
102
|
-
# The file must have a top-level +FIELDS+ key. Core OFX fields cannot be overridden.
|
|
103
|
-
#
|
|
104
|
-
# Raises Errors::ConfigurationError if the file is missing, malformed, references
|
|
105
|
-
# unknown sections, or attempts to override a core field mapping.
|
|
106
|
-
#
|
|
107
|
-
# === Example: Load a custom mappings file
|
|
108
|
-
#
|
|
109
|
-
# OFX.configure do |config|
|
|
110
|
-
# config.load_mappings 'config/my_ofx_mappings.yml'
|
|
111
|
-
# end
|
|
112
|
-
#
|
|
113
|
-
# === Example: Expected YAML format
|
|
114
|
-
#
|
|
115
|
-
# # config/my_ofx_mappings.yml
|
|
116
|
-
# FIELDS:
|
|
117
|
-
# STMTTRN:
|
|
118
|
-
# MYFIELD: my_attribute
|
|
119
|
-
def load_mappings(path)
|
|
120
|
-
raise Errors::ConfigurationError, "Mappings file not found: #{path}" unless File.exist?(path)
|
|
121
|
-
|
|
122
|
-
raw = YAML.safe_load_file(path)
|
|
123
|
-
raise Errors::ConfigurationError, 'Invalid mappings file: expected a Hash' unless raw.is_a?(Hash)
|
|
124
|
-
|
|
125
|
-
fields = raw.fetch('FIELDS') do
|
|
126
|
-
raise Errors::ConfigurationError, "Invalid mappings file: missing top-level 'FIELDS' key"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
fields.each { |tag, mappings| merge_user_section(tag, mappings) }
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
def merge_user_section(xml_tag, mappings)
|
|
135
|
-
unless @sections.key?(xml_tag.to_s)
|
|
136
|
-
raise Errors::ConfigurationError, "Unknown section '#{xml_tag}'. Valid sections: #{@sections.keys.join(', ')}"
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
unless mappings.is_a?(Hash)
|
|
140
|
-
raise Errors::ConfigurationError, "Mapping value for '#{xml_tag}' must be a Hash, got #{mappings.class}"
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
mappings.each_key { |k| assert_not_core!(xml_tag, k) }
|
|
144
|
-
|
|
145
|
-
@user_fields[xml_tag.to_s] ||= {}
|
|
146
|
-
@user_fields[xml_tag.to_s].merge!(mappings)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def assert_not_core!(xml_tag, xml_key)
|
|
150
|
-
core_attr = @core_fields.dig(xml_tag.to_s, xml_key.to_s)
|
|
151
|
-
return unless core_attr
|
|
152
|
-
|
|
153
|
-
raise Errors::ConfigurationError,
|
|
154
|
-
"Cannot override core mapping '#{xml_tag}.#{xml_key}' (reserved as '#{core_attr}')"
|
|
91
|
+
(@core_fields[tag] || {})
|
|
92
|
+
.merge(@default_fields[tag] || {})
|
|
93
|
+
.merge(@user_fields[tag] || {})
|
|
155
94
|
end
|
|
156
95
|
end
|
|
157
96
|
end
|
|
@@ -23,7 +23,7 @@ module OFX
|
|
|
23
23
|
def currency_for(node, section)
|
|
24
24
|
xml_tag = OFX.config.xml_mappings_for(section).key('currency')
|
|
25
25
|
value = xml_tag && text_at(node, xml_tag)
|
|
26
|
-
raise
|
|
26
|
+
raise OFX::Error::InvalidBody, 'Missing required CURDEF tag' if value.nil? || value.empty?
|
|
27
27
|
|
|
28
28
|
value
|
|
29
29
|
end
|
|
@@ -16,7 +16,7 @@ module OFX
|
|
|
16
16
|
# Maps +xml_key+ (the OFX XML element name, String) to a Ruby attribute name
|
|
17
17
|
# via the +to+ keyword (String or Symbol) for this section.
|
|
18
18
|
#
|
|
19
|
-
# Raises
|
|
19
|
+
# Raises ConfigurationError if +xml_key+ is a core-protected field or has already been mapped.
|
|
20
20
|
#
|
|
21
21
|
# === Example: Map a custom bank-specific field
|
|
22
22
|
#
|
|
@@ -27,11 +27,17 @@ module OFX
|
|
|
27
27
|
def map(xml_key, to:)
|
|
28
28
|
core_attr = @core_fields.dig(@xml_tag.to_s, xml_key.to_s)
|
|
29
29
|
if core_attr
|
|
30
|
-
raise OFX::
|
|
30
|
+
raise OFX::Error::InvalidConfiguration,
|
|
31
31
|
"Cannot override core mapping '#{@xml_tag}.#{xml_key}' (reserved as '#{core_attr}')"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
@user_fields[@xml_tag] ||= {}
|
|
35
|
+
|
|
36
|
+
if @user_fields[@xml_tag].key?(xml_key)
|
|
37
|
+
raise OFX::Error::InvalidConfiguration,
|
|
38
|
+
"Duplicate mapping for '#{@xml_tag}.#{xml_key}' — already mapped to '#{@user_fields[@xml_tag][xml_key]}'"
|
|
39
|
+
end
|
|
40
|
+
|
|
35
41
|
@user_fields[@xml_tag][xml_key] = to
|
|
36
42
|
end
|
|
37
43
|
end
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module OFX
|
|
4
4
|
# Represents a credit card account parsed from an OFX statement.
|
|
5
|
+
#
|
|
6
|
+
# === Example
|
|
7
|
+
#
|
|
8
|
+
# account = OFX.new("credit_card.ofx").account
|
|
9
|
+
# account.account_id #=> "4111111111111111"
|
|
10
|
+
# account.currency #=> "USD"
|
|
11
|
+
# account.balance #=> #<OFX::Balance ...>
|
|
12
|
+
# account.transactions #=> #<OFX::TransactionCollection ...>
|
|
5
13
|
class CreditCardAccount < Base::Account
|
|
6
14
|
# Credit card account number (String).
|
|
7
15
|
attr_accessor :account_id
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
module OFX
|
|
4
4
|
# Represents a complete credit card statement parsed from an OFX file,
|
|
5
5
|
# aggregating the account, its transactions, and the closing balance.
|
|
6
|
+
#
|
|
7
|
+
# === Example
|
|
8
|
+
#
|
|
9
|
+
# stmt = OFX.new("credit_card.ofx").statements.first
|
|
10
|
+
# stmt.account #=> #<OFX::CreditCardAccount ...>
|
|
11
|
+
# stmt.balance #=> #<OFX::Balance ...>
|
|
12
|
+
# stmt.transactions #=> #<OFX::TransactionCollection ...>
|
|
13
|
+
# stmt.credit_card_statement? #=> true
|
|
6
14
|
class CreditCardStatement < Base::Statement
|
|
7
15
|
# Always +true+.
|
|
8
16
|
def credit_card_statement? = true
|
data/lib/ofx_kit/errors/error.rb
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module OFX
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
# with <tt>rescue OFX::Errors::Error</tt>.
|
|
7
|
-
module Errors
|
|
8
|
-
# Base error class for all OFX-related exceptions.
|
|
9
|
-
class Error < StandardError; end
|
|
10
|
-
end
|
|
4
|
+
# Base class for all OFX Kit exceptions. Rescue from this to catch any gem error.
|
|
5
|
+
class Error < StandardError; end
|
|
11
6
|
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OFX
|
|
4
|
+
class Error
|
|
5
|
+
# Raised when a singular helper (e.g. +#account+, +#balance+) is called
|
|
6
|
+
# on a file that contains multiple statements. Use +#accounts+ or +#balances+ instead.
|
|
7
|
+
class MultipleStatements < Error; end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OFX
|
|
4
|
+
class Error
|
|
5
|
+
# Raised when the OFX version declared in the header is not supported.
|
|
6
|
+
class UnsupportedVersion < Error
|
|
7
|
+
attr_reader :version
|
|
8
|
+
|
|
9
|
+
def initialize(version)
|
|
10
|
+
@version = version
|
|
11
|
+
super("Unsupported OFX version: #{version}")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors/error'
|
|
4
|
+
require_relative 'errors/parse'
|
|
5
|
+
require_relative 'errors/invalid_header'
|
|
6
|
+
require_relative 'errors/invalid_body'
|
|
7
|
+
require_relative 'errors/unsupported_version'
|
|
8
|
+
require_relative 'errors/unsupported_encoding'
|
|
9
|
+
require_relative 'errors/invalid_configuration'
|
|
10
|
+
require_relative 'errors/multiple_statements'
|
data/lib/ofx_kit/parser.rb
CHANGED
|
@@ -47,7 +47,7 @@ module OFX
|
|
|
47
47
|
##
|
|
48
48
|
# Returns the account for files containing a single statement
|
|
49
49
|
# (BankAccount, CreditCardAccount, or +nil+).
|
|
50
|
-
# Raises
|
|
50
|
+
# Raises Error::MultipleStatements if the file contains more than one statement.
|
|
51
51
|
#
|
|
52
52
|
# === Example
|
|
53
53
|
#
|
|
@@ -57,7 +57,7 @@ module OFX
|
|
|
57
57
|
# ofx.account.bank_id #=> "021000021" # BankAccount only
|
|
58
58
|
def account
|
|
59
59
|
if statements.length > 1
|
|
60
|
-
raise
|
|
60
|
+
raise Error::MultipleStatements, 'File contains multiple statements. Use `accounts` to get all accounts.'
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
statements.first&.account
|
|
@@ -98,7 +98,7 @@ module OFX
|
|
|
98
98
|
|
|
99
99
|
##
|
|
100
100
|
# Returns the balance for files containing a single statement (Balance or +nil+).
|
|
101
|
-
# Raises
|
|
101
|
+
# Raises Error::MultipleStatements if the file contains more than one statement.
|
|
102
102
|
#
|
|
103
103
|
# === Example
|
|
104
104
|
#
|
|
@@ -108,7 +108,7 @@ module OFX
|
|
|
108
108
|
# ofx.balance.posted_at #=> 2024-01-31 00:00:00 +0000
|
|
109
109
|
def balance
|
|
110
110
|
if statements.length > 1
|
|
111
|
-
raise
|
|
111
|
+
raise Error::MultipleStatements, 'File contains multiple statements. Use `balances` to get all balances.'
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
statements.first&.balance
|
|
@@ -14,7 +14,7 @@ module OFX
|
|
|
14
14
|
content = convert_to_utf8(@content)
|
|
15
15
|
parts = content.split(/<OFX>/i, 2)
|
|
16
16
|
|
|
17
|
-
raise
|
|
17
|
+
raise OFX::Error::InvalidHeader, 'Missing <OFX> tag in OFX file' if parts.size < 2
|
|
18
18
|
|
|
19
19
|
@headers = parse_headers(parts[0])
|
|
20
20
|
@body = parse_body("<OFX>#{parts[1]}")
|
|
@@ -35,7 +35,7 @@ module OFX
|
|
|
35
35
|
def parse_body(raw)
|
|
36
36
|
normalized = normalize_sgml(raw)
|
|
37
37
|
doc = Nokogiri::XML(normalized, &:nonet)
|
|
38
|
-
raise
|
|
38
|
+
raise OFX::Error::InvalidBody, "OFX body could not be parsed: #{doc.errors.first}" if doc.errors.any?
|
|
39
39
|
|
|
40
40
|
doc
|
|
41
41
|
end
|
|
@@ -11,7 +11,7 @@ module OFX
|
|
|
11
11
|
content = convert_to_utf8(@content)
|
|
12
12
|
doc = Nokogiri::XML(content, &:nonet)
|
|
13
13
|
|
|
14
|
-
raise
|
|
14
|
+
raise OFX::Error::InvalidBody, "OFX2 body could not be parsed: #{doc.errors.first}" if doc.errors.any?
|
|
15
15
|
|
|
16
16
|
@headers = parse_headers(doc)
|
|
17
17
|
@body = doc
|
data/lib/ofx_kit/version.rb
CHANGED
data/lib/ofx_kit.rb
CHANGED
|
@@ -8,14 +8,7 @@ require 'stringio'
|
|
|
8
8
|
require 'time'
|
|
9
9
|
|
|
10
10
|
require_relative 'ofx_kit/version'
|
|
11
|
-
require_relative 'ofx_kit/errors
|
|
12
|
-
require_relative 'ofx_kit/errors/parse_error'
|
|
13
|
-
require_relative 'ofx_kit/errors/invalid_header_error'
|
|
14
|
-
require_relative 'ofx_kit/errors/invalid_body_error'
|
|
15
|
-
require_relative 'ofx_kit/errors/unsupported_version_error'
|
|
16
|
-
require_relative 'ofx_kit/errors/encoding_error'
|
|
17
|
-
require_relative 'ofx_kit/errors/configuration_error'
|
|
18
|
-
require_relative 'ofx_kit/errors/multiple_statements_error'
|
|
11
|
+
require_relative 'ofx_kit/errors'
|
|
19
12
|
require_relative 'ofx_kit/configuration/core'
|
|
20
13
|
require_relative 'ofx_kit/configuration/section_proxy'
|
|
21
14
|
require_relative 'ofx_kit/configuration/date_parser'
|
|
@@ -78,13 +71,13 @@ module OFX
|
|
|
78
71
|
#
|
|
79
72
|
# :yields: config
|
|
80
73
|
#
|
|
81
|
-
# Raises
|
|
74
|
+
# Raises OFX::Error::InvalidConfiguration if the block raises any error.
|
|
82
75
|
def configure
|
|
83
76
|
yield config
|
|
84
|
-
rescue
|
|
77
|
+
rescue Error::InvalidConfiguration
|
|
85
78
|
raise
|
|
86
79
|
rescue StandardError => e
|
|
87
|
-
raise
|
|
80
|
+
raise Error::InvalidConfiguration, e.message
|
|
88
81
|
end
|
|
89
82
|
|
|
90
83
|
##
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ofx_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucas Geron
|
|
@@ -65,20 +65,6 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '13.0'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: rdoc
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - "~>"
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: 7.2.0
|
|
75
|
-
type: :development
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - "~>"
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: 7.2.0
|
|
82
68
|
- !ruby/object:Gem::Dependency
|
|
83
69
|
name: rspec
|
|
84
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -116,8 +102,8 @@ extra_rdoc_files: []
|
|
|
116
102
|
files:
|
|
117
103
|
- LICENSE
|
|
118
104
|
- README.md
|
|
119
|
-
- lib/generators/ofx_kit/
|
|
120
|
-
- lib/generators/ofx_kit/templates/
|
|
105
|
+
- lib/generators/ofx_kit/install_generator.rb
|
|
106
|
+
- lib/generators/ofx_kit/templates/ofx_kit.rb
|
|
121
107
|
- lib/ofx_kit.rb
|
|
122
108
|
- lib/ofx_kit/balance.rb
|
|
123
109
|
- lib/ofx_kit/bank_account.rb
|
|
@@ -133,14 +119,15 @@ files:
|
|
|
133
119
|
- lib/ofx_kit/configuration/section_proxy.rb
|
|
134
120
|
- lib/ofx_kit/credit_card_account.rb
|
|
135
121
|
- lib/ofx_kit/credit_card_statement.rb
|
|
136
|
-
- lib/ofx_kit/errors
|
|
137
|
-
- lib/ofx_kit/errors/encoding_error.rb
|
|
122
|
+
- lib/ofx_kit/errors.rb
|
|
138
123
|
- lib/ofx_kit/errors/error.rb
|
|
139
|
-
- lib/ofx_kit/errors/
|
|
140
|
-
- lib/ofx_kit/errors/
|
|
141
|
-
- lib/ofx_kit/errors/
|
|
142
|
-
- lib/ofx_kit/errors/
|
|
143
|
-
- lib/ofx_kit/errors/
|
|
124
|
+
- lib/ofx_kit/errors/invalid_body.rb
|
|
125
|
+
- lib/ofx_kit/errors/invalid_configuration.rb
|
|
126
|
+
- lib/ofx_kit/errors/invalid_header.rb
|
|
127
|
+
- lib/ofx_kit/errors/multiple_statements.rb
|
|
128
|
+
- lib/ofx_kit/errors/parse.rb
|
|
129
|
+
- lib/ofx_kit/errors/unsupported_encoding.rb
|
|
130
|
+
- lib/ofx_kit/errors/unsupported_version.rb
|
|
144
131
|
- lib/ofx_kit/mappings/core_mappings.yml
|
|
145
132
|
- lib/ofx_kit/mappings/field_mappings.yml
|
|
146
133
|
- lib/ofx_kit/parser.rb
|
|
@@ -150,6 +137,7 @@ files:
|
|
|
150
137
|
- lib/ofx_kit/transaction.rb
|
|
151
138
|
- lib/ofx_kit/transaction_collection.rb
|
|
152
139
|
- lib/ofx_kit/version.rb
|
|
140
|
+
homepage: https://github.com/lucasgeron/ofx_kit
|
|
153
141
|
licenses:
|
|
154
142
|
- MIT
|
|
155
143
|
metadata:
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators'
|
|
4
|
-
|
|
5
|
-
module OFX
|
|
6
|
-
# Namespace for Rails generator classes provided by the ofx_kit gem.
|
|
7
|
-
module Generators
|
|
8
|
-
# Ejects OFX field mappings into the Rails application so they can be customized.
|
|
9
|
-
#
|
|
10
|
-
# Creates +config/initializers/ofx_mappings.yml+ with the gem's default field
|
|
11
|
-
# mappings. The file is auto-detected and loaded by the OFX gem on boot —
|
|
12
|
-
# no initializer or +OFX.configure+ call is needed.
|
|
13
|
-
#
|
|
14
|
-
# === Example
|
|
15
|
-
#
|
|
16
|
-
# rails generate ofx_kit:eject
|
|
17
|
-
class EjectGenerator < Rails::Generators::Base
|
|
18
|
-
source_root File.expand_path('templates', __dir__)
|
|
19
|
-
|
|
20
|
-
desc 'Ejects OFX field mappings into config/initializers/ofx_mappings.yml'
|
|
21
|
-
|
|
22
|
-
def eject_mappings
|
|
23
|
-
copy_file 'ofx_mappings.yml', 'config/initializers/ofx_mappings.yml'
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# config/initializers/ofx_mappings.yml
|
|
2
|
-
#
|
|
3
|
-
# Each value is the Ruby attribute name exposed on the domain object.
|
|
4
|
-
# Example: changing FITID from "fit_id" to "uid" means
|
|
5
|
-
# transaction.fit_id → transaction.uid
|
|
6
|
-
#
|
|
7
|
-
# Bank-specific XML tags not listed below can also be captured.
|
|
8
|
-
# Simply add them to the relevant section:
|
|
9
|
-
#
|
|
10
|
-
# STMTTRN:
|
|
11
|
-
# HISPAYEEMEMO: "extended_memo" # → transaction.extended_memo
|
|
12
|
-
# BANKACCTFROM:
|
|
13
|
-
# AGENCIA: "branch_code" # → account.branch_code
|
|
14
|
-
#
|
|
15
|
-
FIELDS:
|
|
16
|
-
STMTTRN:
|
|
17
|
-
FITID: "fit_id"
|
|
18
|
-
TRNTYPE: "type"
|
|
19
|
-
NAME: "name"
|
|
20
|
-
MEMO: "memo"
|
|
21
|
-
PAYEE: "payee"
|
|
22
|
-
CHECKNUM: "check_number"
|
|
23
|
-
REFNUM: "ref_number"
|
|
24
|
-
SIC: "sic"
|
|
25
|
-
|
|
26
|
-
BANKACCTFROM:
|
|
27
|
-
BANKID: "bank_id"
|
|
28
|
-
ACCTID: "account_id"
|
|
29
|
-
ACCTTYPE: "account_type"
|
|
30
|
-
BRANCHID: "branch_id"
|
|
31
|
-
|
|
32
|
-
CCACCTFROM:
|
|
33
|
-
ACCTID: "account_id"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OFX
|
|
4
|
-
module Errors
|
|
5
|
-
##
|
|
6
|
-
# Raised when the OFX version declared in the file is not supported.
|
|
7
|
-
class UnsupportedVersionError < Error
|
|
8
|
-
##
|
|
9
|
-
# The unsupported version string found in the file (String).
|
|
10
|
-
attr_reader :version
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
# Creates a new error for the given unsupported +version+ string.
|
|
14
|
-
def initialize(version)
|
|
15
|
-
@version = version
|
|
16
|
-
super("Unsupported OFX version: #{version}")
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|