lib/twitter_cldr/js/mustache/implementation/numbers/numbers.coffee (223 lines of code) (raw):
# Copyright 2012 Twitter, Inc
# http://www.apache.org/licenses/LICENSE-2.0
class TwitterCldr.NumberFormatter
constructor: ->
@tokens = []
@default_symbols =
'group': ','
'decimal': '.'
'plus_sign': '+'
'minus_sign': '-'
@data: ->
TwitterCldr.get_data().NumberFormatter
all_tokens : ->
@constructor.data().all_tokens
symbols : ->
@constructor.data().symbols
format: (number, options = {}) ->
opts = @default_format_options_for(number)
for key, val of options
opts[key] = if options[key]? then options[key] else opts[key]
number = @round_to(number, opts.precision || 0)
tokens = @get_tokens(number, opts)
if tokens?
if tokens not instanceof Array
tokens_sample = tokens[Object.keys(tokens)[0]]
truncated_number = @truncate_number(number, tokens_sample[1].length)
rounded_num = Number(@parse_number(truncated_number, opts)[0]) * Number(@get_key(number)) / (10 ** (tokens_sample[1].length - 1))
# Checking _stack_depth should be a redundant condition, as
# @get_key(rounded_num) should always equal @get_key(number) the second
# time around. We only check it to prevent infinite loops in case of
# bugs
if !opts._stack_depth && @get_key(rounded_num) != @get_key(number)
return @format(rounded_num, Object.assign(opts, { _stack_depth: 1 }))
truncated_number = Math.floor(truncated_number) if opts.precision == 0
tokens = tokens[TwitterCldr.PluralRules.rule_for(truncated_number)]
[prefix, suffix, integer_format, fraction_format] = @partition_tokens(tokens)
number_part = @truncate_number(number, integer_format.format.length)
[intg, fraction] = @parse_number(number_part, opts)
result = integer_format.apply(parseFloat(intg), opts)
result += fraction_format.apply(fraction, opts) if fraction
sign = if number_part < 0 && prefix != "-" then @symbols().minus_sign || @default_symbols.minus_sign else ""
"#{prefix}#{result}#{suffix}"
else
unless @ instanceof TwitterCldr.DecimalFormatter
# No specific formatting pattern for this number in current locale, so
# we fall back to DecimalFormatter
new TwitterCldr.DecimalFormatter().format(number, opts)
else
# Use default toString if current instance is already a DecimalFormatter
number.toString()
truncate_number: (number, decimal_digits) ->
number # noop for base class
partition_tokens: (tokens) ->
[
tokens[0] || "",
tokens[2] || "",
new TwitterCldr.NumberFormatter.IntegerHelper(tokens[1], @symbols()),
new TwitterCldr.NumberFormatter.FractionHelper(tokens[1], @symbols())
]
parse_number: (number, options = {}) ->
if options.precision?
precision = options.precision
else
precision = this.precision_from(number)
number = this.round_to(number, precision)
Math.abs(number).toFixed(precision).split(".")
precision_from: (num) ->
parts = num.toString().split(".")
if parts.length == 2 then parts[1].length else 0
round_to: (number, precision) ->
factor = Math.pow(10, precision)
Math.round(number * factor) / factor
get_tokens: ->
throw new Error("get_tokens() not implemented - use a derived class like PercentFormatter.")
class TwitterCldr.PercentFormatter extends TwitterCldr.NumberFormatter
constructor: (options = {}) ->
@default_percent_sign = "%"
super
format: (number, options = {}) ->
super(number, options).replace('¤', @symbols().percent_sign || @default_percent_sign)
default_format_options_for: (number) ->
precision: 0
get_tokens: (number, options) ->
if number < 0 then @all_tokens().percent.negative else @all_tokens().percent.positive
class TwitterCldr.DecimalFormatter extends TwitterCldr.NumberFormatter
constructor: (@data_reader) ->
super
format: (number, options = {}) ->
try
result = super(number, options)
if @data_reader? and options["type"]?
transliterator = TwitterCldr.NumberingSystems.for_name(
@data_reader.number_system_for(options["type"])
)
if transliterator?
result = transliterator.transliterate(result)
result
catch error
number.toString()
default_format_options_for: (number) ->
precision: this.precision_from(number)
get_tokens: (number, options = {}) ->
if number < 0 then @all_tokens().decimal.negative else @all_tokens().decimal.positive
class TwitterCldr.CurrencyFormatter extends TwitterCldr.NumberFormatter
constructor: (options = {}) ->
@default_currency_symbol = "$"
@default_precision = @currencies_data().DEFAULT.digits
super
currencies_data: ->
TwitterCldr.get_data().CurrencyFormatter.currencies_data
format: (number, options = {}) ->
if options.currency
if TwitterCldr.Currencies?
currency = TwitterCldr.Currencies.for_code(options.currency)
currency ||= symbol: options.currency
else
currency = symbol: options.currency
unless options.precision?
options.precision = @defaults_for_currency(options.currency).digits
else
currency = symbol: @default_currency_symbol
symbol = if options.use_cldr_symbol then currency.cldr_symbol else currency.symbol
super(number, options).replace('¤', symbol)
default_format_options_for: (number) ->
precision = this.precision_from(number)
if precision == 0 then precision = @default_precision
precision: precision
get_tokens: (number, options = {}) ->
if number < 0 then @all_tokens().currency.negative else @all_tokens().currency.positive
defaults_for_currency: (currency) ->
@currencies_data()[currency] || @currencies_data().DEFAULT
class TwitterCldr.AbbreviatedNumberFormatter extends TwitterCldr.NumberFormatter
NUMBER_MAX: Math.pow(10, 15)
NUMBER_MIN: 1000
default_format_options_for: (number) ->
precision: this.precision_from(number)
get_type: ->
"decimal"
get_key: (number) ->
zeroes = ("0" for i in [0...(Math.floor(number).toString().length - 1)]).join("")
"1#{zeroes}"
get_tokens: (number, options = {}) ->
# TODO: should this work for negative numbers?
type = if (number < @NUMBER_MAX) && (number >= @NUMBER_MIN) then this.get_type() else "decimal"
format = if type == this.get_type() then this.get_key(number) else null
tokens = @all_tokens()[type]
tokens = if number < 0 then tokens.negative else tokens.positive
tokens = tokens[format] if format?
tokens
truncate_number: (number, decimal_digits) ->
if @NUMBER_MIN <= number and number < @NUMBER_MAX
factor = Math.max(0, Math.floor(number).toString().length - decimal_digits)
number / Math.pow(10, factor)
else
number
class TwitterCldr.ShortDecimalFormatter extends TwitterCldr.AbbreviatedNumberFormatter
get_type: ->
"short_decimal"
class TwitterCldr.LongDecimalFormatter extends TwitterCldr.AbbreviatedNumberFormatter
get_type: ->
"long_decimal"
class TwitterCldr.NumberFormatter.BaseHelper
interpolate: (string, value, orientation = "right") ->
value = value.toString()
length = value.length
start = if orientation == "left" then 0 else -length
string = (("#" for i in [0...length]).join("") + string).slice(-length) if string.length < length
if start < 0
string = string[0...(start + string.length)] + value
else
string = string[0...start] + value + string[(length)..-1]
string.replace(/#/g, "")
class TwitterCldr.NumberFormatter.IntegerHelper extends TwitterCldr.NumberFormatter.BaseHelper
constructor: (token, symbols = {}) ->
format = token.split('.')[0]
@format = this.prepare_format(format, symbols)
@groups = this.parse_groups(format)
@separator = symbols.group || ','
apply: (number, options = {}) ->
this.format_groups(this.interpolate(@format, parseInt(number)))
format_groups: (string) ->
return string if @groups.length == 0
tokens = []
primary_group = @groups[@groups.length - 1]
secondary_group = @groups[0]
[string, token] = @chop_group(string, secondary_group)
tokens.push(token)
while string.length
[string, token] = @chop_group(string, primary_group)
tokens.push(token)
(token for token in tokens when token != null).reverse().join(@separator)
parse_groups: (format) ->
index = format.lastIndexOf(',')
return [] unless index > 0
rest = format[0...index]
widths = [format.length - index - 1]
widths.push(rest.length - rest.lastIndexOf(',') - 1) if rest.lastIndexOf(',') > -1
widths = (width for width in widths when width != null) # compact
widths.reverse() # uniq
(widths[index] for index in [0...widths.length] when widths.indexOf(widths[index], index + 1) == -1).reverse()
chop_group: (string, size) ->
start = Math.max(string.length - size, 0)
[string.slice(0, start), string.slice(start)]
prepare_format: (format, symbols) ->
format.replace /[,+-]/g, (match) ->
switch match
when ',' then ''
when '+' then symbols.plus_sign
when '-' then symbols.minus_sign
class TwitterCldr.NumberFormatter.FractionHelper extends TwitterCldr.NumberFormatter.BaseHelper
constructor: (token, symbols = {}) ->
@format = if token then token.split('.').pop() else ""
@decimal = symbols.decimal || "."
@precision = @format.length
apply: (fraction, options = {}) ->
precision = if options.precision? then options.precision else @precision
if precision > 0
@decimal + this.interpolate(this.format_for(options), fraction, "left")
else
""
format_for: (options) ->
precision = if options.precision? then options.precision else @precision
if precision then ("0" for i in [0...precision]).join("") else @format