Skip to content

Commit 8c17436

Browse files
authored
Merge pull request #200 from RubenIgnacio/improve_parsing_delimiters
Improve parsing delimiters
2 parents 70f502a + 1a00828 commit 8c17436

File tree

2 files changed

+70
-43
lines changed

2 files changed

+70
-43
lines changed

lib/monetize/parser.rb

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@ def parse
5151

5252
negative, num = extract_sign(num)
5353

54-
num.chop! if num =~ /[\.|,]$/
55-
56-
major, minor = extract_major_minor(num)
57-
58-
amount = to_big_decimal([major, minor].join(DEFAULT_DECIMAL_MARK))
54+
amount = to_big_decimal(normalize_number(num))
5955
amount = apply_multiplier(multiplier_exp, amount)
6056
amount = apply_sign(negative, amount)
6157

@@ -64,6 +60,12 @@ def parse
6460

6561
private
6662

63+
def normalize_number(num)
64+
clean_num = num.sub(/[\.|,]$/, "")
65+
66+
extract_major_minor(clean_num).join(DEFAULT_DECIMAL_MARK)
67+
end
68+
6769
def to_big_decimal(value)
6870
BigDecimal(value)
6971
rescue ::ArgumentError => err
@@ -108,64 +110,65 @@ def compute_currency
108110
CURRENCY_SYMBOLS[match.to_s] if match
109111
end
110112

113+
def remove_separator(num, separator)
114+
num.gsub(separator, "")
115+
end
116+
111117
def extract_major_minor(num)
112118
used_delimiters = num.scan(/[^\d]/).uniq
113119

114120
case used_delimiters.length
115121
when 0
116122
[num, DEFAULT_MINOR]
117-
when 2
118-
thousands_separator, decimal_mark = used_delimiters
119-
split_major_minor(num.gsub(thousands_separator, ''), decimal_mark)
120123
when 1
121124
extract_major_minor_with_single_delimiter(num, used_delimiters.first)
125+
when 2
126+
thousands_separator, decimal_mark = used_delimiters
127+
num = remove_separator(num, thousands_separator)
128+
129+
split_major_minor(num, decimal_mark)
122130
else
123131
fail ParseError, 'Invalid amount'
124132
end
125133
end
126134

127-
def minor_has_correct_dp_for_currency_subunit?(minor)
128-
minor.length == currency.subunit_to_unit.to_s.length - 1
135+
def minor_has_correct_decimal_places_for_currency?(minor)
136+
minor.length == currency.decimal_places
129137
end
130138

131139
def extract_major_minor_with_single_delimiter(num, delimiter)
132140
if expect_whole_subunits?
133-
_possible_major, possible_minor = split_major_minor(num, delimiter)
134-
if minor_has_correct_dp_for_currency_subunit?(possible_minor)
135-
split_major_minor(num, delimiter)
136-
else
137-
extract_major_minor_with_tentative_delimiter(num, delimiter)
138-
end
139-
else
140-
if delimiter == currency.decimal_mark
141-
split_major_minor(num, delimiter)
142-
elsif Monetize.enforce_currency_delimiters && delimiter == currency.thousands_separator
143-
[num.gsub(delimiter, ''), DEFAULT_MINOR]
144-
else
145-
extract_major_minor_with_tentative_delimiter(num, delimiter)
141+
possible_major, possible_minor = split_major_minor(num, delimiter)
142+
143+
if minor_has_correct_decimal_places_for_currency?(possible_minor)
144+
return [possible_major, possible_minor]
146145
end
146+
elsif delimiter == currency.decimal_mark
147+
return split_major_minor(num, delimiter)
148+
elsif Monetize.enforce_currency_delimiters && delimiter == currency.thousands_separator
149+
return [remove_separator(num, delimiter), DEFAULT_MINOR]
147150
end
151+
152+
extract_major_minor_with_tentative_delimiter(num, delimiter)
148153
end
149154

150155
def extract_major_minor_with_tentative_delimiter(num, delimiter)
151156
if num.scan(delimiter).length > 1
152157
# Multiple matches; treat as thousands separator
153-
[num.gsub(delimiter, ''), DEFAULT_MINOR]
154-
else
155-
possible_major, possible_minor = split_major_minor(num, delimiter)
158+
return [remove_separator(num, delimiter), DEFAULT_MINOR]
159+
end
156160

157-
# Doesn't look like thousands separator
158-
is_decimal_mark = possible_minor.length != 3 ||
159-
possible_major.length > 3 ||
160-
possible_major.to_i == 0 ||
161-
(!expect_whole_subunits? && delimiter == '.')
161+
possible_major, possible_minor = split_major_minor(num, delimiter)
162162

163-
if is_decimal_mark
164-
[possible_major, possible_minor]
165-
else
166-
["#{possible_major}#{possible_minor}", DEFAULT_MINOR]
167-
end
168-
end
163+
# Doesn't look like thousands separator
164+
is_decimal_mark = possible_minor.length != 3 ||
165+
possible_major.length > 3 ||
166+
possible_major.to_i == 0 ||
167+
(!expect_whole_subunits? && delimiter == ".")
168+
169+
return [possible_major, possible_minor] if is_decimal_mark
170+
171+
["#{possible_major}#{possible_minor}", DEFAULT_MINOR]
169172
end
170173

171174
def extract_multiplier

spec/monetize_spec.rb

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,37 @@
257257
expect(Monetize.parse('19.12.89', 'EUR')).to eq Money.new(191_289_00, 'EUR')
258258
end
259259

260-
it 'parses correctly strings with exactly 3 decimal digits' do
261-
expect(Monetize.parse('6,534', 'EUR')).to eq Money.new(653, 'EUR')
262-
expect(Monetize.parse('6.534', 'EUR')).to eq Money.new(653, 'EUR')
260+
context "parses with enforce_currency_delimiters enabled" do
261+
before(:all) { Monetize.enforce_currency_delimiters = true }
262+
after(:all) { Monetize.enforce_currency_delimiters = false }
263263

264-
Monetize.enforce_currency_delimiters = true
265-
expect(Monetize.parse('6.534', 'EUR')).to eq Money.new(6_534_00, 'EUR')
266-
Monetize.enforce_currency_delimiters = false
264+
it "parses strings with correct currency delimiters" do
265+
expect(Monetize.parse("34,56 EUR")).to eq Money.new(34_56, "EUR")
266+
expect(Monetize.parse("34.56 USD")).to eq Money.new(34_56, "USD")
267+
end
268+
269+
it "parses strings with incorrect currency delimiters" do
270+
expect(Monetize.parse("6.53 EUR")).to eq Money.new(653_00, "EUR")
271+
expect(Monetize.parse("6,53 USD")).to eq Money.new(653_00, "USD")
272+
end
273+
274+
it "parses strings with exactly 3 digits after the separator" do
275+
expect(Monetize.parse('6,534', 'EUR')).to eq Money.new(6_53, 'EUR')
276+
expect(Monetize.parse('6,534', 'USD')).to eq Money.new(6_534_00, 'USD')
277+
278+
expect(Monetize.parse('6.534', 'EUR')).to eq Money.new(6_534_00, 'EUR')
279+
expect(Monetize.parse('6.534', 'USD')).to eq Money.new(6_53, 'USD')
280+
end
281+
282+
it "parses strings with multiple thousands separators" do
283+
expect(Monetize.parse("1.234.567", "EUR")).to eq Money.new(1_234_567_00, "EUR")
284+
expect(Monetize.parse("1,234,567", "USD")).to eq Money.new(1_234_567_00, "USD")
285+
end
286+
287+
it "returns nil for strings with invalid thousands separators" do
288+
expect(Monetize.parse("12,34,56 EUR")).to be_nil
289+
expect(Monetize.parse("12.34.56 USD")).to be_nil
290+
end
267291
end
268292

269293
context 'Money object attempting to be parsed' do

0 commit comments

Comments
 (0)