2
2
3
3
module Monetize
4
4
class Parser
5
- CURRENCY_SYMBOLS = {
5
+ INITIAL_CURRENCY_SYMBOLS = {
6
6
'$' => 'USD' ,
7
7
'€' => 'EUR' ,
8
8
'£' => 'GBP' ,
@@ -28,15 +28,30 @@ class Parser
28
28
'S$' => 'SGD' ,
29
29
'HK$' => 'HKD' ,
30
30
'NT$' => 'TWD' ,
31
- '₱' => 'PHP' ,
32
- }
31
+ '₱' => 'PHP'
32
+ } . freeze
33
+ # FIXME: This ignored symbols could be ambiguous or conflict with other symbols
34
+ IGNORED_SYMBOLS = [ 'kr' , 'NIO$' , 'UM' , 'L' , 'oz t' , "so'm" , 'CUC$' ] . freeze
33
35
34
36
MULTIPLIER_SUFFIXES = { 'K' => 3 , 'M' => 6 , 'B' => 9 , 'T' => 12 }
35
37
MULTIPLIER_SUFFIXES . default = 0
36
38
MULTIPLIER_REGEXP = Regexp . new ( format ( '^(.*?\d)(%s)\b([^\d]*)$' , MULTIPLIER_SUFFIXES . keys . join ( '|' ) ) , 'i' )
37
39
38
40
DEFAULT_DECIMAL_MARK = '.' . freeze
39
41
42
+ def self . currency_symbols
43
+ @@currency_symbols ||= Money ::Currency . table . reduce ( INITIAL_CURRENCY_SYMBOLS . dup ) do |memo , ( _ , currency ) |
44
+ symbol = currency [ :symbol ]
45
+ symbol = currency [ :disambiguate_symbol ] if symbol && memo . key? ( symbol )
46
+
47
+ next memo if is_invalid_currency_symbol? ( symbol )
48
+
49
+ memo [ symbol ] = currency [ :iso_code ] unless memo . value? ( currency [ :iso_code ] )
50
+
51
+ memo
52
+ end . freeze
53
+ end
54
+
40
55
def initialize ( input , fallback_currency = Money . default_currency , options = { } )
41
56
@input = input . to_s . strip
42
57
@fallback_currency = fallback_currency
@@ -65,6 +80,17 @@ def parse
65
80
66
81
private
67
82
83
+ def self . is_invalid_currency_symbol? ( symbol )
84
+ currency_symbol_blank? ( symbol ) ||
85
+ symbol . include? ( '.' ) || # Ignore symbols with dots because they can be confused with decimal marks
86
+ IGNORED_SYMBOLS . include? ( symbol ) ||
87
+ MULTIPLIER_REGEXP . match? ( "1#{ symbol } " ) # Ignore symbols that can be confused with multipliers
88
+ end
89
+
90
+ def self . currency_symbol_blank? ( symbol )
91
+ symbol . nil? || symbol . empty?
92
+ end
93
+
68
94
def to_big_decimal ( value )
69
95
BigDecimal ( value )
70
96
rescue ::ArgumentError => err
@@ -74,11 +100,8 @@ def to_big_decimal(value)
74
100
attr_reader :input , :fallback_currency , :options
75
101
76
102
def parse_currency
77
- computed_currency = nil
78
- computed_currency = input [ /[A-Z]{2,3}/ ]
79
- computed_currency = nil unless Monetize ::Parser ::CURRENCY_SYMBOLS . value? ( computed_currency )
80
- computed_currency ||= compute_currency if assume_from_symbol?
81
-
103
+ computed_currency = compute_currency_from_iso_code
104
+ computed_currency ||= compute_currency_from_symbol if assume_from_symbol?
82
105
83
106
computed_currency || fallback_currency || Money . default_currency
84
107
end
@@ -99,9 +122,18 @@ def apply_sign(negative, amount)
99
122
negative ? amount * -1 : amount
100
123
end
101
124
102
- def compute_currency
125
+ def compute_currency_from_iso_code
126
+ computed_currency = input [ /[A-Z]{2,4}/ ]
127
+
128
+ return unless computed_currency
129
+
130
+ computed_currency if self . class . currency_symbols . value? ( computed_currency )
131
+ end
132
+
133
+ def compute_currency_from_symbol
103
134
match = input . match ( currency_symbol_regex )
104
- CURRENCY_SYMBOLS [ match . to_s ] if match
135
+
136
+ self . class . currency_symbols [ match . to_s ] if match
105
137
end
106
138
107
139
def extract_major_minor ( num , currency )
@@ -127,20 +159,19 @@ def minor_has_correct_dp_for_currency_subunit?(minor, currency)
127
159
def extract_major_minor_with_single_delimiter ( num , currency , delimiter )
128
160
if expect_whole_subunits?
129
161
possible_major , possible_minor = split_major_minor ( num , delimiter )
162
+
130
163
if minor_has_correct_dp_for_currency_subunit? ( possible_minor , currency )
131
- split_major_minor ( num , delimiter )
132
- else
133
- extract_major_minor_with_tentative_delimiter ( num , delimiter )
164
+ return [ possible_major , possible_minor ]
134
165
end
135
166
else
136
- if delimiter == currency . decimal_mark
137
- split_major_minor ( num , delimiter )
138
- elsif Monetize . enforce_currency_delimiters && delimiter == currency . thousands_separator
139
- [ num . gsub ( delimiter , '' ) , 0 ]
140
- else
141
- extract_major_minor_with_tentative_delimiter ( num , delimiter )
167
+ return split_major_minor ( num , delimiter ) if delimiter == currency . decimal_mark
168
+
169
+ if Monetize . enforce_currency_delimiters && delimiter == currency . thousands_separator
170
+ return [ num . gsub ( delimiter , '' ) , 0 ]
142
171
end
143
172
end
173
+
174
+ extract_major_minor_with_tentative_delimiter ( num , delimiter )
144
175
end
145
176
146
177
def extract_major_minor_with_tentative_delimiter ( num , delimiter )
@@ -165,7 +196,9 @@ def extract_major_minor_with_tentative_delimiter(num, delimiter)
165
196
end
166
197
167
198
def extract_multiplier
168
- if ( matches = MULTIPLIER_REGEXP . match ( input ) )
199
+ matches = MULTIPLIER_REGEXP . match ( input )
200
+
201
+ if matches
169
202
multiplier_suffix = matches [ 2 ] . upcase
170
203
[ MULTIPLIER_SUFFIXES [ multiplier_suffix ] , "#{ $1} #{ $3} " ]
171
204
else
@@ -180,7 +213,7 @@ def extract_sign(input)
180
213
end
181
214
182
215
def regex_safe_symbols
183
- CURRENCY_SYMBOLS . keys . map { |key | Regexp . escape ( key ) } . join ( '|' )
216
+ self . class . currency_symbols . keys . map { |key | Regexp . escape ( key ) } . join ( '|' )
184
217
end
185
218
186
219
def split_major_minor ( num , delimiter )
0 commit comments