dojo.provide("dojo.i18n.number");
dojo.require("dojo.experimental");
dojo.experimental("dojo.i18n.number");
dojo.require("dojo.regexp");
dojo.require("dojo.i18n.common");
dojo.require("dojo.lang.common");
/**
* Method to Format and validate a given number
*
* @param Number value
* The number to be formatted and validated.
* @param Object flags
* flags.places number of decimal places to show, default is 0 (cannot be Infinity)
* flags.round true to round the number, false to truncate
* @param String locale
* The locale used to determine the number format.
* @return String
* the formatted number of type String if successful
* or null if an unsupported locale value was provided
**/
dojo.i18n.number.format = function(value, flags /*optional*/, locale /*optional*/){
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
if (typeof flags.decimal == "undefined") {flags.decimal = formatData[2];}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
if (typeof flags.round == "undefined") {flags.round = true;}
if (typeof flags.signed == "undefined") {flags.signed = true;}
var output = (flags.signed && (value < 0)) ? "-" : "";
value = Math.abs(value);
var whole = String((((flags.places > 0) || !flags.round) ? Math.floor : Math.round)(value));
// Splits str into substrings of size count, starting from right to left. Is there a more clever way to do this in JS?
function splitSubstrings(str, count){
for(var subs = []; str.length >= count; str = str.substr(0, str.length - count)){
subs.push(str.substr(-count));
}
if (str.length > 0){subs.push(str);}
return subs.reverse();
}
if (flags.groupSize2 && (whole.length > flags.groupSize)){
var groups = splitSubstrings(whole.substr(0, whole.length - flags.groupSize), flags.groupSize2);
groups.push(whole.substr(-flags.groupSize));
output = output + groups.join(flags.separator);
}else if (flags.groupSize){
output = output + splitSubstrings(whole, flags.groupSize).join(flags.separator);
}else{
output = output + whole;
}
//TODO: what if flags.places is Infinity?
if (flags.places > 0){
//Q: Is it safe to convert to a string and split on ".", or might that be locale dependent? Use Math for now.
var fract = value - Math.floor(value);
fract = (flags.round ? Math.round : Math.floor)(fract * Math.pow(10, flags.places));
output = output + flags.decimal + fract;
}
//TODO: exp
return output;
};
/**
* Method to convert a properly formatted int string to a primative numeric value.
*
* @param String value
* The int string to be convertted
* @param string locale
* The locale used to convert the number string
* @param Object flags
* flags.validate true to check the string for strict adherence to the locale settings for separator, sign, etc.
* Default is true
* @return Number
* Returns a value of type Number, Number.NaN if not a number, or null if locale is not supported.
**/
dojo.i18n.number.parse = function(value, locale /*optional*/, flags /*optional*/){
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
if (typeof flags.decimal == "undefined") {flags.decimal = formatData[2];}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
if (typeof flags.validate == "undefined") {flags.validate = true;}
if (flags.validate && !dojo.i18n.number.isReal(value, locale, flags)) {
return Number.NaN;
}
var numbers = value.split(flags.decimal);
if (numbers.length > 2){return Number.NaN; }
var whole = Number(numbers[0].replace(new RegExp("\\" + flags.separator, "g"), ""));
var fract = (numbers.length == 1) ? 0 : Number(numbers[1]) / Math.pow(10, String(numbers[1]).length); // could also do Number(whole + "." + numbers[1]) if whole != NaN
//TODO: exp
return whole + fract;
};
/**
Validates whether a string is in an integer format.
@param value A string.
@param locale the locale to determine formatting used. By default, the locale defined by the
host environment: dojo.locale
@param flags An object.
flags.signed The leading plus-or-minus sign. Can be true, false, or [true, false].
Default is [true, false], (i.e. sign is optional).
flags.separator The character used as the thousands separator. Default is specified by the locale.
For more than one symbol use an array, e.g. [",", ""], makes ',' optional.
The empty array [] makes the default separator optional.
@return true or false.
*/
dojo.i18n.number.isInteger = function(value, locale /*optional*/, flags /*optional*/) {
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
else if (dojo.lang.isArray(flags.separator) && flags.separator.length ===0){
flags.separator = [formatData[1],""];
}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
var re = new RegExp("^" + dojo.regexp.integer(flags) + "$");
return re.test(value);
};
/**
Validates whether a string is a real valued number.
Format is the usual exponential notation.
@param value A string.
@param locale the locale to determine formatting used. By default, the locale defined by the
host environment: dojo.locale
@param flags An object.
flags.places The integer number of decimal places.
If not given, the decimal part is optional and the number of places is unlimited.
flags.decimal The character used for the decimal point. The default is specified by the locale.
flags.exponent Express in exponential notation. Can be true, false, or [true, false].
Default is [true, false], (i.e. the exponential part is optional).
flags.eSigned The leading plus-or-minus sign on the exponent. Can be true, false,
or [true, false]. Default is [true, false], (i.e. sign is optional).
flags in regexp.integer can be applied.
@return true or false.
*/
dojo.i18n.number.isReal = function(value, locale /*optional*/, flags /*optional*/) {
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
else if (dojo.lang.isArray(flags.separator) && flags.separator.length ===0){
flags.separator = [formatData[1],""];
}
if (typeof flags.decimal == "undefined") {flags.decimal = formatData[2];}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
var re = new RegExp("^" + dojo.regexp.realNumber(flags) + "$");
return re.test(value);
};
//TODO: hide in a closure?
//TODO: change to use hashes and mixins, rather than arrays
//Q: fallback algorithm/how to structure table:
// does it make sense to look by country code most of the time (wildcard match on
// language, except where it's relevant) and provide default country when only
// a language is given?
(function() {
dojo.i18n.number.FORMAT_TABLE = {
//0: thousand seperator for monetary, 1: thousand seperator for number, 2: decimal seperator, 3: group size, 4: second group size because of india
'ar-ae': ["","", ",", 1],
'ar-bh': ["","",",", 1],
'ar-dz': ["","",",", 1],
'ar-eg': ["","", ",", 1],
'ar-jo': ["","",",", 1],
'ar-kw': ["","", ",", 1],
'ar-lb': ["","", ",", 1],
'ar-ma': ["","", ",", 1],
'ar-om': ["","", ",", 1],
'ar-qa': ["","", ",", 1],
'ar-sa': ["","", ",", 1],
'ar-sy': ["","", ",", 1],
'ar-tn': ["","", ",", 1],
'ar-ye': ["","", ",", 1],
'cs-cz': [".",".", ",", 3],
'da-dk': [".",".", ",", 3],
'de-at': [".",".", ",", 3],
'de-de': [".",".", ",", 3],
'de-lu': [".",".", ",", 3],
//IBM JSL defect 51278. right now we have problem with single quote. //IBM: explain?
'de-ch': ["'","'", ".", 3], //Q: comma as decimal separator for currency??
//'de-ch': [".",".", ",", 3],
'el-gr': [".",".", ",", 3],
'en-au': [",",",", ".", 3],
'en-ca': [",",",", ".", 3],
'en-gb': [",",",", ".", 3],
'en-hk': [",",",", ".", 3],
'en-ie': [",",",", ".", 3],
'en-in': [",",",", ".", 3,2],//india-english, 1,23,456.78
'en-nz': [",",",", ".", 3],
'en-us': [",",",", ".", 3],
'en-za': [",",",", ".", 3],
'es-ar': [".",".", ",", 3],
'es-bo': [".",".", ",", 3],
'es-cl': [".",".", ",", 3],
'es-co': [".",".", ",", 3],
'es-cr': [".",".", ",", 3],
'es-do': [".",".", ",", 3],
'es-ec': [".",".", ",", 3],
'es-es': [".",".", ",", 3],
'es-gt': [",",",", ".", 3],
'es-hn': [",",",", ".", 3],
'es-mx': [",",",", ".", 3],
'es-ni': [",",",", ".", 3],
'es-pa': [",",",", ".", 3],
'es-pe': [",",",", ".", 3],
'es-pr': [",",",", ".", 3],
'es-py': [".",".",",", 3],
'es-sv': [",", ",",".", 3],
'es-uy': [".",".",",", 3],
'es-ve': [".",".", ",", 3],
'fi-fi': [" "," ", ",", 3],
'fr-be': [".",".",",", 3],
'fr-ca': [" ", " ", ",", 3],
'fr-ch': [" ", " ",".", 3],
'fr-fr': [" "," ", ",", 3],
'fr-lu': [".",".", ",", 3],
'he-il': [",",",", ".", 3],
'hu-hu': [" ", " ",",", 3],
'it-ch': [" "," ", ".", 3],
'it-it': [".",".", ",", 3],
'ja-jp': [",",",", ".", 3],
'ko-kr': [",", ",",".", 3],
'no-no': [".",".", ",", 3],
'nl-be': [" "," ", ",", 3],
'nl-nl': [".",".", ",", 3],
'pl-pl': [".", ".",",", 3],
'pt-br': [".",".", ",", 3],
'pt-pt': [".",".", "$", 3],
'ru-ru': [" ", " ",",", 3],
'sv-se': ["."," ", ",", 3],
'tr-tr': [".",".", ",", 3],
'zh-cn': [",",",", ".", 3],
'zh-hk': [",",",",".", 3],
'zh-tw': [",", ",",".", 3],
'*': [",",",", ".", 3]
};
})();
dojo.i18n.number._mapToLocalizedFormatData = function(table, locale){
locale = dojo.hostenv.normalizeLocale(locale);
//TODO: most- to least-specific search? search by country code?
//TODO: implement aliases to simplify and shorten tables
var data = table[locale];
if (typeof data == 'undefined'){data = table['*'];}
return data;
}