money-joshm1 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,355 @@
1
+ # encoding: utf-8
2
+
3
+ require 'json'
4
+
5
+ class Money
6
+
7
+ # Represents a specific currency unit.
8
+ class Currency
9
+ include Comparable
10
+
11
+ require "money/currency/loader"
12
+ extend Loader
13
+
14
+ require "money/currency/heuristics"
15
+ extend Heuristics
16
+
17
+ # Thrown when an unknown currency is requested.
18
+ class UnknownCurrency < StandardError; end
19
+
20
+ class << self
21
+
22
+ # Lookup a currency with given +id+ an returns a +Currency+ instance on
23
+ # success, +nil+ otherwise.
24
+ #
25
+ # @param [String, Symbol, #to_s] id Used to look into +table+ and
26
+ # retrieve the applicable attributes.
27
+ #
28
+ # @return [Money::Currency]
29
+ #
30
+ # @example
31
+ # Money::Currency.find(:eur) #=> #<Money::Currency id: eur ...>
32
+ # Money::Currency.find(:foo) #=> nil
33
+ def find(id)
34
+ id = id.to_s.downcase.to_sym
35
+ new(id) if self.table[id]
36
+ end
37
+
38
+ # Lookup a currency with given +num+ as an ISO 4217 numeric and returns an
39
+ # +Currency+ instance on success, +nil+ otherwise.
40
+ #
41
+ # @param [#to_s] num used to look into +table+ in +iso_numeric+ and find
42
+ # the right currency id.
43
+ #
44
+ # @return [Money::Currency]
45
+ #
46
+ # @example
47
+ # Money::Currency.find_by_iso_numeric(978) #=> #<Money::Currency id: eur ...>
48
+ # Money::Currency.find_by_iso_numeric('001') #=> nil
49
+ def find_by_iso_numeric(num)
50
+ num = num.to_s
51
+ id, _ = self.table.find{|key, currency| currency[:iso_numeric] == num}
52
+ new(id) if self.table[id]
53
+ end
54
+
55
+ # Wraps the object in a +Currency+ unless it's already a +Currency+
56
+ # object.
57
+ #
58
+ # @param [Object] object The object to attempt and wrap as a +Currency+
59
+ # object.
60
+ #
61
+ # @return [Money::Currency]
62
+ #
63
+ # @example
64
+ # c1 = Money::Currency.new(:usd)
65
+ # Money::Currency.wrap(nil) #=> nil
66
+ # Money::Currency.wrap(c1) #=> #<Money::Currency id: usd ...>
67
+ # Money::Currency.wrap("usd") #=> #<Money::Currency id: usd ...>
68
+ def wrap(object)
69
+ if object.nil?
70
+ nil
71
+ elsif object.is_a?(Currency)
72
+ object
73
+ else
74
+ Currency.new(object)
75
+ end
76
+ end
77
+
78
+ # List of known currencies.
79
+ #
80
+ # == monetary unit
81
+ # The standard unit of value of a currency, as the dollar in the United States or the peso in Mexico.
82
+ # http://www.answers.com/topic/monetary-unit
83
+ # == fractional monetary unit, subunit
84
+ # A monetary unit that is valued at a fraction (usually one hundredth) of the basic monetary unit
85
+ # http://www.answers.com/topic/fractional-monetary-unit-subunit
86
+ #
87
+ # See http://en.wikipedia.org/wiki/List_of_circulating_currencies and
88
+ # http://search.cpan.org/~tnguyen/Locale-Currency-Format-1.28/Format.pm
89
+ def table
90
+ @table ||= load_currencies
91
+ end
92
+
93
+ # List the currencies imported and registered
94
+ # @return [Array]
95
+ #
96
+ # @example
97
+ # Money::Currency.iso_codes()
98
+ # [#<Currency ..USD>, 'CAD', 'EUR']...
99
+ def all
100
+ table.keys.map {|curr| Currency.new(curr)}.sort_by(&:priority)
101
+ end
102
+
103
+ # We need a string-based validator before creating an unbounded number of symbols.
104
+ # http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol#11
105
+ # https://github.com/RubyMoney/money/issues/132
106
+ def stringified_keys
107
+ @stringified_keys ||= stringify_keys
108
+ end
109
+
110
+ def register(curr)
111
+ key = curr[:iso_code].downcase.to_sym
112
+ @table[key] = curr
113
+ @stringified_keys = stringify_keys
114
+ end
115
+
116
+ private
117
+
118
+ def stringify_keys
119
+ table.keys.map{|k| k.to_s.downcase}
120
+ end
121
+ end
122
+
123
+ # The symbol used to identify the currency, usually the lowercase
124
+ # +iso_code+ attribute.
125
+ #
126
+ # @return [Symbol]
127
+ attr_reader :id
128
+
129
+ # A numerical value you can use to sort/group the currency list.
130
+ #
131
+ # @return [Integer]
132
+ attr_reader :priority
133
+
134
+ # The international 3-letter code as defined by the ISO 4217 standard.
135
+ #
136
+ # @return [String]
137
+ attr_reader :iso_code
138
+ #
139
+ # The international 3-numeric code as defined by the ISO 4217 standard.
140
+ #
141
+ # @return [String]
142
+ attr_reader :iso_numeric
143
+
144
+ # The currency name.
145
+ #
146
+ # @return [String]
147
+ attr_reader :name
148
+
149
+ # The currency symbol (UTF-8 encoded).
150
+ #
151
+ # @return [String]
152
+ attr_reader :symbol
153
+
154
+ # The html entity for the currency symbol
155
+ #
156
+ # @return [String]
157
+ attr_reader :html_entity
158
+
159
+ # The name of the fractional monetary unit.
160
+ #
161
+ # @return [String]
162
+ attr_reader :subunit
163
+
164
+ # The proportion between the unit and the subunit
165
+ #
166
+ # @return [Integer]
167
+ attr_reader :subunit_to_unit
168
+
169
+ # The decimal mark, or character used to separate the whole unit from the subunit.
170
+ #
171
+ # @return [String]
172
+ attr_reader :decimal_mark
173
+ alias :separator :decimal_mark
174
+
175
+ # The character used to separate thousands grouping of the whole unit.
176
+ #
177
+ # @return [String]
178
+ attr_reader :thousands_separator
179
+ alias :delimiter :thousands_separator
180
+
181
+ # Should the currency symbol precede the amount, or should it come after?
182
+ #
183
+ # @return [Boolean]
184
+ attr_reader :symbol_first
185
+
186
+ # Create a new +Currency+ object.
187
+ #
188
+ # @param [String, Symbol, #to_s] id Used to look into +table+ and retrieve
189
+ # the applicable attributes.
190
+ #
191
+ # @return [Money::Currency]
192
+ #
193
+ # @example
194
+ # Money::Currency.new(:usd) #=> #<Money::Currency id: usd ...>
195
+ def initialize(id)
196
+ id = id.to_s.downcase
197
+ raise(UnknownCurrency, "Unknown currency `#{id}'") unless self.class.stringified_keys.include?(id)
198
+
199
+ @id = id.to_sym
200
+ data = self.class.table[@id]
201
+ data.each_pair do |key, value|
202
+ instance_variable_set(:"@#{key}", value)
203
+ end
204
+ end
205
+
206
+ # Compares +self+ with +other_currency+ against the value of +priority+
207
+ # attribute.
208
+ #
209
+ # @param [Money::Currency] other_currency The currency to compare to.
210
+ #
211
+ # @return [-1,0,1] -1 if less than, 0 is equal to, 1 if greater than
212
+ #
213
+ # @example
214
+ # c1 = Money::Currency.new(:usd)
215
+ # c2 = Money::Currency.new(:jpy)
216
+ # c1 <=> c2 #=> 1
217
+ # c2 <=> c1 #=> -1
218
+ # c1 <=> c1 #=> 0
219
+ def <=>(other_currency)
220
+ self.priority <=> other_currency.priority
221
+ end
222
+
223
+ # Compares +self+ with +other_currency+ and returns +true+ if the are the
224
+ # same or if their +id+ attributes match.
225
+ #
226
+ # @param [Money::Currency] other_currency The currency to compare to.
227
+ #
228
+ # @return [Boolean]
229
+ #
230
+ # @example
231
+ # c1 = Money::Currency.new(:usd)
232
+ # c2 = Money::Currency.new(:jpy)
233
+ # c1 == c1 #=> true
234
+ # c1 == c2 #=> false
235
+ def ==(other_currency)
236
+ self.equal?(other_currency) ||
237
+ self.id.to_s.downcase == (other_currency.is_a?(Currency) ? other_currency.id.to_s.downcase : other_currency.to_s.downcase)
238
+ end
239
+
240
+ # Compares +self+ with +other_currency+ and returns +true+ if the are the
241
+ # same or if their +id+ attributes match.
242
+ #
243
+ # @param [Money::Currency] other_currency The currency to compare to.
244
+ #
245
+ # @return [Boolean]
246
+ #
247
+ # @example
248
+ # c1 = Money::Currency.new(:usd)
249
+ # c2 = Money::Currency.new(:jpy)
250
+ # c1.eql? c1 #=> true
251
+ # c1.eql? c2 #=> false
252
+ def eql?(other_currency)
253
+ self == other_currency
254
+ end
255
+
256
+ # Returns a Fixnum hash value based on the +id+ attribute in order to use
257
+ # functions like & (intersection), group_by, etc.
258
+ #
259
+ # @return [Fixnum]
260
+ #
261
+ # @example
262
+ # Money::Currency.new(:usd).hash #=> 428936
263
+ def hash
264
+ id.hash
265
+ end
266
+
267
+ # Returns a human readable representation.
268
+ #
269
+ # @return [String]
270
+ #
271
+ # @example
272
+ # Money::Currency.new(:usd) #=> #<Currency id: usd ...>
273
+ def inspect
274
+ "#<#{self.class.name} id: #{id}, priority: #{priority}, symbol_first: #{symbol_first}, thousands_separator: #{thousands_separator}, html_entity: #{html_entity}, decimal_mark: #{decimal_mark}, name: #{name}, symbol: #{symbol}, subunit_to_unit: #{subunit_to_unit}, exponent: #{exponent}, iso_code: #{iso_code}, iso_numeric: #{iso_numeric}, subunit: #{subunit}>"
275
+ end
276
+
277
+ # Returns a string representation corresponding to the upcase +id+
278
+ # attribute.
279
+ #
280
+ # -–
281
+ # DEV: id.to_s.upcase corresponds to iso_code but don't use ISO_CODE for consistency.
282
+ #
283
+ # @return [String]
284
+ #
285
+ # @example
286
+ # Money::Currency.new(:usd).to_s #=> "USD"
287
+ # Money::Currency.new(:eur).to_s #=> "EUR"
288
+ def to_s
289
+ id.to_s.upcase
290
+ end
291
+
292
+ # Conversation to +self+.
293
+ #
294
+ # @return [self]
295
+ def to_currency
296
+ self
297
+ end
298
+
299
+
300
+ # Returns currency symbol or iso code for currencies with no symbol.
301
+ #
302
+ # @return [String]
303
+ def code
304
+ symbol || iso_code
305
+ end
306
+
307
+ def symbol_first?
308
+ !!@symbol_first
309
+ end
310
+
311
+ # Returns the number of digits after the decimal separator.
312
+ #
313
+ # @return [Float]
314
+ def exponent
315
+ Math.log10(@subunit_to_unit)
316
+ end
317
+
318
+ # Cache decimal places for subunit_to_unit values. Common ones pre-cached.
319
+ def self.decimal_places_cache
320
+ @decimal_places_cache ||= {
321
+ 1 => 0,
322
+ 10 => 1,
323
+ 100 => 2,
324
+ 1000 => 3
325
+ }
326
+ end
327
+
328
+ # The number of decimal places needed.
329
+ #
330
+ # @return [Integer]
331
+ def decimal_places
332
+ cache = self.class.decimal_places_cache
333
+ places = cache[subunit_to_unit]
334
+ unless places
335
+ places = calculate_decimal_places(subunit_to_unit)
336
+ cache[subunit_to_unit] = places
337
+ end
338
+ places
339
+ end
340
+
341
+ # If we need to figure out how many decimal places we need we
342
+ # use repeated integer division.
343
+ def calculate_decimal_places(num)
344
+ return 0 if num == 1
345
+ i = 1
346
+ while num >= 10
347
+ num /= 10
348
+ i += 1 if num >= 10
349
+ end
350
+ i
351
+ end
352
+ private :calculate_decimal_places
353
+
354
+ end
355
+ end
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+
3
+ module Money::Currency::Heuristics
4
+
5
+ # An robust and efficient algorithm for finding currencies in
6
+ # text. Using several algorithms it can find symbols, iso codes and
7
+ # even names of currencies.
8
+ # Although not recommendable, it can also attempt to find the given
9
+ # currency in an entire sentence
10
+ #
11
+ # Returns: Array (matched results)
12
+ def analyze(str)
13
+ return Analyzer.new(str, search_tree).process
14
+ end
15
+
16
+ private
17
+
18
+ # Build a search tree from the currency database
19
+ def search_tree
20
+ @_search_tree ||= {
21
+ :by_symbol => currencies_by_symbol,
22
+ :by_iso_code => currencies_by_iso_code,
23
+ :by_name => currencies_by_name
24
+ }
25
+ end
26
+
27
+ def currencies_by_symbol
28
+ {}.tap do |r|
29
+ table.each do |dummy, c|
30
+ symbol = (c[:symbol]||"").downcase
31
+ symbol.chomp!('.')
32
+ (r[symbol] ||= []) << c
33
+
34
+ (c[:alternate_symbols]||[]).each do |ac|
35
+ ac = ac.downcase
36
+ ac.chomp!('.')
37
+ (r[ac] ||= []) << c
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def currencies_by_iso_code
44
+ {}.tap do |r|
45
+ table.each do |dummy,c|
46
+ (r[c[:iso_code].downcase] ||= []) << c
47
+ end
48
+ end
49
+ end
50
+
51
+ def currencies_by_name
52
+ {}.tap do |r|
53
+ table.each do |dummy,c|
54
+ name_parts = c[:name].downcase.split
55
+ name_parts.each {|part| part.chomp!('.')}
56
+
57
+ # construct one branch per word
58
+ root = r
59
+ while name_part = name_parts.shift
60
+ root = (root[name_part] ||= {})
61
+ end
62
+
63
+ # the leaf is a currency
64
+ (root[:value] ||= []) << c
65
+ end
66
+ end
67
+ end
68
+
69
+ class Analyzer
70
+ attr_reader :search_tree, :words
71
+ attr_accessor :str, :currencies
72
+
73
+ def initialize str, search_tree
74
+ @str = (str||'').dup
75
+ @search_tree = search_tree
76
+ @currencies = []
77
+ end
78
+
79
+ def process
80
+ format
81
+ return [] if str.empty?
82
+
83
+ search_by_symbol
84
+ search_by_iso_code
85
+ search_by_name
86
+
87
+ prepare_reply
88
+ end
89
+
90
+ def format
91
+ str.gsub!(/[\r\n\t]/,'')
92
+ str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
93
+ str.gsub!(/[0-9]/, '')
94
+ str.downcase!
95
+ @words = str.split
96
+ @words.each {|word| word.chomp!('.'); word.chomp!(',') }
97
+ end
98
+
99
+ def search_by_symbol
100
+ words.each do |word|
101
+ if found = search_tree[:by_symbol][word]
102
+ currencies.concat(found)
103
+ end
104
+ end
105
+ end
106
+
107
+ def search_by_iso_code
108
+ words.each do |word|
109
+ if found = search_tree[:by_iso_code][word]
110
+ currencies.concat(found)
111
+ end
112
+ end
113
+ end
114
+
115
+ def search_by_name
116
+ # remember, the search tree by name is a construct of branches and leaf!
117
+ # We need to try every combination of words within the sentence, so we
118
+ # end up with a x^2 equation, which should be fine as most names are either
119
+ # one or two words, and this is multiplied with the words of given sentence
120
+
121
+ search_words = words.dup
122
+
123
+ while search_words.length > 0
124
+ root = search_tree[:by_name]
125
+
126
+ search_words.each do |word|
127
+ if root = root[word]
128
+ if root[:value]
129
+ currencies.concat(root[:value])
130
+ end
131
+ else
132
+ break
133
+ end
134
+ end
135
+
136
+ search_words.delete_at(0)
137
+ end
138
+ end
139
+
140
+ def prepare_reply
141
+ codes = currencies.map do |currency|
142
+ currency[:iso_code]
143
+ end
144
+ codes.uniq!
145
+ codes.sort!
146
+ codes
147
+ end
148
+ end
149
+ end