// Number and String formatting functions from Google Closure Library
// http://code.google.com/closure/library/
// /library/format/format.js
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright © 2011 Andrew Plummer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sub-license, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice, and every other copyright notice found in this
// software, and all the attributions in every file, and this permission notice
// shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED
//
/**
* @module montage/core/converter/number-converter
* @requires montage/core/converter/converter
*/
var Converter = require("./converter").Converter;
var Validator = require("./converter").Validator;
var isNumber = require("./converter").isNumber;
var isDef = require("./converter").isDef;
require('../shim/string');
/**
* Regular expression for detecting scaling units, such as K, M, G, etc. for
* converting a string representation to a numeric value. Also allow 'k' to be
* aliased to 'K'. These could be used for SI (powers of 1000) or Binary
* (powers of 1024) conversions. Also allow final 'B' to be interpreted as
* byte-count, implicitly triggering binary conversion (e.g., '10.2MB').
* @type {RegExp}
* @memberof module:montage/core/converter#
* @private
*/
var SCALED_NUMERIC_RE_ = /^([\-]?\d+\.?\d*)([K,M,G,T,P,k,m,u,n]?)[B]?$/;
/**
* Ordered list of scaling prefixes in decreasing order.
* @memberof module:montage/converter#
* @type {Array}
* @private
*/
// kishore - changed prefix 'G' to 'B' to represent Billion
var NUMERIC_SCALE_PREFIXES_ = [
'P', 'T', 'B', 'M', 'K', '', 'm', 'u', 'n'
];
/**
* Scaling factors for conversion of numeric value to string. SI conversion.
* @memberof module:montage/converter#
* @type {Object}
* @private
*/
var NUMERIC_SCALES_SI_ = exports.NUMERIC_SCALES_SI_ = {
'': 1,
'n': 1e-9,
'u': 1e-6,
'm': 1e-3,
'k': 1e3,
'K': 1e3,
'M': 1e6,
'B': 1e9,
'T': 1e12,
'P': 1e15
};
/**
* Scaling factors for conversion of numeric value to string. Binary conversion.
* @memberof module:montage/converter#
* @type {Object}
* @private
*/
var NUMERIC_SCALES_BINARY_ = exports.NUMERIC_SCALES_BINARY_ = {
'': 1,
'n': Math.pow(1024, -3),
'u': Math.pow(1024, -2),
'm': 1.0 / 1024,
'k': 1024,
'K': 1024,
'M': Math.pow(1024, 2),
'G': Math.pow(1024, 3),
'T': Math.pow(1024, 4),
'P': Math.pow(1024, 5)
};
/**
* Converts a numeric value to string, using specified conversion scales.
* @memberof module:montage/converter#
* @param {number} val Value to be converted.
* @param {Object} conversion Dictionary of scaling factors.
* @param {number} optDecimals The number of decimals to use. Default is 2.
* @param {string} optSuffix Optional suffix to append.
* @returns {string} The human readable form of the byte size.
* @private
*/
var _numericValueToString = exports._numericValueToString = function (val, conversion, optDecimals, optSuffix, prefixes) {
prefixes = prefixes || NUMERIC_SCALE_PREFIXES_;
var origVal = val;
var symbol = '';
var scale = 1;
if (val < 0) {
val = -val;
}
for (var i = 0; i < prefixes.length; i++) {
var unit = prefixes[i];
scale = conversion[unit];
if (val >= scale || (scale <= 1 && val > 0.1 * scale)) {
// Treat values less than 1 differently, allowing 0.5 to be "0.5" rather
// than "500m"
symbol = unit;
break;
}
}
if (!symbol) {
scale = 1;
} else if (optSuffix) {
symbol += optSuffix;
}
var ex = Math.pow(10, isDef(optDecimals) ? optDecimals : 2);
return Math.round(origVal / scale * ex) / ex + symbol;
};
/**
* Converts a string to numeric value, taking into account the units.
* @memberof module:montage/converter#
* @param {string} stringValue String to be converted to numeric value.
* @param {Object} conversion Dictionary of conversion scales.
* @returns {number} Numeric value for string. If it cannot be converted, returns NaN.
* @private
*/
var _stringToNumericValue = function (stringValue, conversion) {
var match = stringValue.match(SCALED_NUMERIC_RE_);
if (!match) {
return NaN;
}
return match[1] * conversion[match[2]];
};
/**
* Checks whether string value containing scaling units (K, M, G, T, P, m, u,
* n) can be converted to a number.
* Where there is a decimal, there must be a digit to the left of the decimal
* point.
* Negative numbers are valid.
* @example 0, 1, 1.0, 10.4K, 2.3M, -0.3P, 1.2m
* @memberof module:montage/core/converter#
* @function
* @param {string} val String value to check.
* @returns {boolean} true If the string could be converted to a numeric value.
*/
var isConvertableScaledNumber = function (val) {
return SCALED_NUMERIC_RE_.test(val);
};
/**
* Converts a string to numeric value, taking into account the units.
* If string ends in 'B', use binary conversion.
* @memberof module:montage/core/converter#
* @function
* @param {string} stringValue String to be converted to numeric value.
* @returns {number} Numeric value for string.
*/
var stringToNumericValue = exports.stringToNumericValue = function (stringValue) {
if (stringValue.endsWith('B')) {
return _stringToNumericValue(
stringValue, NUMERIC_SCALES_BINARY_);
}
return _stringToNumericValue(
stringValue, NUMERIC_SCALES_SI_);
};
/**
* Converts a numeric value to string representation. SI conversion.
* @memberof module:montage/core/converter#
* @function
* @param {number} val Value to be converted.
* @param {number} optDecimals The number of decimals to use. Defaults to 2.
* @returns {string} String representation of number.
*/
var numericValueToString = exports.numericValueToString = function (val, optDecimals) {
return _numericValueToString(val, NUMERIC_SCALES_SI_, optDecimals);
};
/**
* @class NumberValidator
* @classdesc Validates that a string can be represented as a numeric value, and returns the numeric value.
* @extends Validator
*/
var NumberValidator = exports.NumberValidator = Validator.specialize( /** @lends NumberValidator# */ {
/**
* Indicates whether floating point values are allowed.
* If `true` (the default) then the validator attempts to parse the string
* as a float value.
* If `false`, it attempts to parse the value as an integer.
* @type {Property}
* @default {boolean} true
*/
allowFloat: {
value: true
},
/**
* @type {Property}
* @default {boolean} true
*/
allowNegative: {
value: true
},
/**
* Determines if the parameter `v` is a number or not.
* @function
* @param {string} v The value to validate as a number.
* @returns {number} num An integer or float, if the value provided to the
* function can parsed as a number;
* otherwise returns an error.
*/
validate: {
value: function (v) {
var num;
v = v || '';
v = v.replace(/,/g, '');
if (isNumber(v)) {
num = v;
} else {
num = (this.allowFloat === true ? parseFloat(v, 10) : parseInt(v, 10));
}
if (isNaN(num)) {
// error
return {message: 'Invalid Number'};
} else {
return num;
}
}
}
});
/**
* @class NumberConverter
* @classdesc Formats a number for easier readability.
*/
var NumberConverter = exports.NumberConverter = Converter.specialize( /** @lends NumberConverter# */ {
/**
* Allow partial conversion
* @type {Property}
* @default {boolean} false
*/
allowPartialConversion: {
value: false
},
/**
* @type {Function}
* @default {attribute} NumberValidator Uses this object.
*/
validator: {
value: new NumberValidator()
},
/**
* @type {Property}
* @default {string} null
*/
// valid fn values are:
shorten: {
value: null
},
/**
* @type {Property}
* @default {number} 2
*/
decimals: {
value: 2
},
/**
* @type {Property}
* @default {boolean} false
*/
forceDecimals: {
value: false
},
/**
* @type {Property}
* @default {number} null
*/
round: {
value: null
},
_reg: {
value: /(\d+)(\d{3})/
},
/**
* @type {Property}
* @default {boolean} true
*/
allowFloat: {
value: true
},
/**
* @type {Property}
* @default {boolean} true
*/
allowNegative: {
value: true
},
// credit: sugar.js - https://github.com/andrewplummer/Sugar
_makeReadable: {
value: function (num, comma, period) {
comma = comma || ',';
period = period || '.';
var split = num.toString().split('.');
var numeric = split[0];
while (this._reg.test(numeric)) {
numeric = numeric.replace(this._reg, '$1' + comma + '$2');
}
var afterDecimal = split.length > 1 ? split[1] : '';
if (this.forceDecimals) {
while (afterDecimal.length < this.decimals) {
afterDecimal += '0';
}
}
var decimal = afterDecimal.length > 0 ? period + afterDecimal : '';
return numeric + decimal;
}
},
/**
* @function
* @param {number} value The value to convert.
* @returns {string}
*/
convert: {
value: function (v) {
if (this.shorten) {
// shorten the number to 10K, 100K, 1M etc
return numericValueToString(v, this.decimals);
} else {
var num;
if (this.round) {
num = Number(Math.round(v)).toString();
} else {
var ex = Math.pow(10, this.decimals || 2);
var scale = 1;
num = Number(Math.round(v / scale * ex) / ex);
}
return this._makeReadable(num); //.toString();
}
}
},
/**
* @function
* @param {string} stringValue The string representation of a number.
* @returns {number} The numeric value validated with to {@link
* NumberConverter#validator}.
* @throws {Error} if the return value of {@link
* NumberConverter#validator#validate} is not a number
* @see NumberConverter#validator
* @see NumberConverter#allowFloat
* @see NumberConverter#allowNegative
*/
revert: {
value: function (stringValue) {
// use a Validator to validate first
this.validator.allowFloat = this.allowFloat;
this.validator.allowNegative = this.allowNegative;
var result = this.validator.validate(stringValue);
if (isNumber(result)) {
return result;
} else {
// error object {code, message}
//return null;
throw new Error(result.message);
}
//return parseFloat(stringValue, 10) || null;
}
}
});