e44a7e37b6
git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f
551 lines
18 KiB
JavaScript
551 lines
18 KiB
JavaScript
if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
dojo._hasResource["dojo.number"] = true;
|
|
dojo.provide("dojo.number");
|
|
|
|
dojo.require("dojo.i18n");
|
|
dojo.requireLocalization("dojo.cldr", "number", null, "zh-cn,zh,ko-kr,pt,en-us,en-gb,de,ja,ja-jp,en,ROOT,en-au,fr,es,ko,zh-tw,it,es-es,de-de");
|
|
dojo.require("dojo.string");
|
|
dojo.require("dojo.regexp");
|
|
|
|
|
|
/*=====
|
|
dojo.number = {
|
|
// summary: localized formatting and parsing routines for Number
|
|
}
|
|
|
|
dojo.number.__FormatOptions = function(){
|
|
// pattern: String?
|
|
// override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
|
|
// with this string
|
|
// type: String?
|
|
// choose a format type based on the locale from the following:
|
|
// decimal, scientific, percent, currency. decimal by default.
|
|
// places: Number?
|
|
// fixed number of decimal places to show. This overrides any
|
|
// information in the provided pattern.
|
|
// round: Number?
|
|
// 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
|
|
// means don't round.
|
|
// currency: String?
|
|
// an [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD"
|
|
// symbol: String?
|
|
// localized currency symbol
|
|
// locale: String?
|
|
// override the locale used to determine formatting rules
|
|
this.pattern = pattern;
|
|
this.type = type;
|
|
this.places = places;
|
|
this.round = round;
|
|
this.currency = currency;
|
|
this.symbol = symbol;
|
|
this.locale = locale;
|
|
}
|
|
=====*/
|
|
|
|
dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
|
|
// summary:
|
|
// Format a Number as a String, using locale-specific settings
|
|
// description:
|
|
// Create a string from a Number using a known localized pattern.
|
|
// Formatting patterns appropriate to the locale are chosen from the
|
|
// [CLDR](http://unicode.org/cldr) as well as the appropriate symbols and
|
|
// delimiters. See <http://www.unicode.org/reports/tr35/#Number_Elements>
|
|
// value:
|
|
// the number to be formatted. If not a valid JavaScript number,
|
|
// return null.
|
|
|
|
options = dojo.mixin({}, options || {});
|
|
var locale = dojo.i18n.normalizeLocale(options.locale);
|
|
var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
|
|
options.customs = bundle;
|
|
var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
|
|
if(isNaN(value)){ return null; } // null
|
|
return dojo.number._applyPattern(value, pattern, options); // String
|
|
};
|
|
|
|
//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
|
|
dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
|
|
|
|
dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
|
|
// summary:
|
|
// Apply pattern to format value as a string using options. Gives no
|
|
// consideration to local customs.
|
|
// value:
|
|
// the number to be formatted.
|
|
// pattern:
|
|
// a pattern string as described by
|
|
// [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
|
|
// options: dojo.number.__FormatOptions?
|
|
// _applyPattern is usually called via `dojo.number.format()` which
|
|
// populates an extra property in the options parameter, "customs".
|
|
// The customs object specifies group and decimal parameters if set.
|
|
|
|
//TODO: support escapes
|
|
options = options || {};
|
|
var group = options.customs.group;
|
|
var decimal = options.customs.decimal;
|
|
|
|
var patternList = pattern.split(';');
|
|
var positivePattern = patternList[0];
|
|
pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);
|
|
|
|
//TODO: only test against unescaped
|
|
if(pattern.indexOf('%') != -1){
|
|
value *= 100;
|
|
}else if(pattern.indexOf('\u2030') != -1){
|
|
value *= 1000; // per mille
|
|
}else if(pattern.indexOf('\u00a4') != -1){
|
|
group = options.customs.currencyGroup || group;//mixins instead?
|
|
decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
|
|
pattern = pattern.replace(/\u00a4{1,3}/, function(match){
|
|
var prop = ["symbol", "currency", "displayName"][match.length-1];
|
|
return options[prop] || options.currency || "";
|
|
});
|
|
}else if(pattern.indexOf('E') != -1){
|
|
throw new Error("exponential notation not supported");
|
|
}
|
|
|
|
//TODO: support @ sig figs?
|
|
var numberPatternRE = dojo.number._numberPatternRE;
|
|
var numberPattern = positivePattern.match(numberPatternRE);
|
|
if(!numberPattern){
|
|
throw new Error("unable to find a number expression in pattern: "+pattern);
|
|
}
|
|
return pattern.replace(numberPatternRE,
|
|
dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places}));
|
|
}
|
|
|
|
dojo.number.round = function(/*Number*/value, /*Number*/places, /*Number?*/multiple){
|
|
// summary:
|
|
// Rounds the number at the given number of places
|
|
// value:
|
|
// the number to round
|
|
// places:
|
|
// the number of decimal places where rounding takes place
|
|
// multiple:
|
|
// rounds next place to nearest multiple
|
|
|
|
var pieces = String(value).split(".");
|
|
var length = (pieces[1] && pieces[1].length) || 0;
|
|
if(length > places){
|
|
var factor = Math.pow(10, places);
|
|
if(multiple > 0){factor *= 10/multiple;places++;} //FIXME
|
|
value = Math.round(value * factor)/factor;
|
|
|
|
// truncate to remove any residual floating point values
|
|
pieces = String(value).split(".");
|
|
length = (pieces[1] && pieces[1].length) || 0;
|
|
if(length > places){
|
|
pieces[1] = pieces[1].substr(0, places);
|
|
value = Number(pieces.join("."));
|
|
}
|
|
}
|
|
return value; //Number
|
|
}
|
|
|
|
/*=====
|
|
dojo.number.__FormatAbsoluteOptions = function(){
|
|
// decimal: String?
|
|
// the decimal separator
|
|
// group: String?
|
|
// the group separator
|
|
// places: Integer?
|
|
// number of decimal places
|
|
// round: Number?
|
|
// 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
|
|
// means don't round.
|
|
this.decimal = decimal;
|
|
this.group = group;
|
|
this.places = places;
|
|
this.round = round;
|
|
}
|
|
=====*/
|
|
|
|
dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
|
|
// summary:
|
|
// Apply numeric pattern to absolute value using options. Gives no
|
|
// consideration to local customs.
|
|
// value:
|
|
// the number to be formatted, ignores sign
|
|
// pattern:
|
|
// the number portion of a pattern (e.g. `#,##0.00`)
|
|
options = options || {};
|
|
if(options.places === true){options.places=0;}
|
|
if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit
|
|
|
|
var patternParts = pattern.split(".");
|
|
var maxPlaces = (options.places >= 0) ? options.places : (patternParts[1] && patternParts[1].length) || 0;
|
|
if(!(options.round < 0)){
|
|
value = dojo.number.round(value, maxPlaces, options.round);
|
|
}
|
|
|
|
var valueParts = String(Math.abs(value)).split(".");
|
|
var fractional = valueParts[1] || "";
|
|
if(options.places){
|
|
valueParts[1] = dojo.string.pad(fractional.substr(0, options.places), options.places, '0', true);
|
|
}else if(patternParts[1] && options.places !== 0){
|
|
// Pad fractional with trailing zeros
|
|
var pad = patternParts[1].lastIndexOf("0") + 1;
|
|
if(pad > fractional.length){
|
|
valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
|
|
}
|
|
|
|
// Truncate fractional
|
|
var places = patternParts[1].length;
|
|
if(places < fractional.length){
|
|
valueParts[1] = fractional.substr(0, places);
|
|
}
|
|
}else{
|
|
if(valueParts[1]){ valueParts.pop(); }
|
|
}
|
|
|
|
// Pad whole with leading zeros
|
|
var patternDigits = patternParts[0].replace(',', '');
|
|
pad = patternDigits.indexOf("0");
|
|
if(pad != -1){
|
|
pad = patternDigits.length - pad;
|
|
if(pad > valueParts[0].length){
|
|
valueParts[0] = dojo.string.pad(valueParts[0], pad);
|
|
}
|
|
|
|
// Truncate whole
|
|
if(patternDigits.indexOf("#") == -1){
|
|
valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
|
|
}
|
|
}
|
|
|
|
// Add group separators
|
|
var index = patternParts[0].lastIndexOf(',');
|
|
var groupSize, groupSize2;
|
|
if(index != -1){
|
|
groupSize = patternParts[0].length - index - 1;
|
|
var remainder = patternParts[0].substr(0, index);
|
|
index = remainder.lastIndexOf(',');
|
|
if(index != -1){
|
|
groupSize2 = remainder.length - index - 1;
|
|
}
|
|
}
|
|
var pieces = [];
|
|
for(var whole = valueParts[0]; whole;){
|
|
var off = whole.length - groupSize;
|
|
pieces.push((off > 0) ? whole.substr(off) : whole);
|
|
whole = (off > 0) ? whole.slice(0, off) : "";
|
|
if(groupSize2){
|
|
groupSize = groupSize2;
|
|
delete groupSize2;
|
|
}
|
|
}
|
|
valueParts[0] = pieces.reverse().join(options.group || ",");
|
|
|
|
return valueParts.join(options.decimal || ".");
|
|
};
|
|
|
|
/*=====
|
|
dojo.number.__RegexpOptions = function(){
|
|
// pattern: String?
|
|
// override pattern with this string. Default is provided based on
|
|
// locale.
|
|
// type: String?
|
|
// choose a format type based on the locale from the following:
|
|
// decimal, scientific, percent, currency. decimal by default.
|
|
// locale: String?
|
|
// override the locale used to determine formatting rules
|
|
// strict: Boolean?
|
|
// strict parsing, false by default
|
|
// places: Number|String?
|
|
// number of decimal places to accept: Infinity, a positive number, or
|
|
// a range "n,m". By default, defined by pattern.
|
|
this.pattern = pattern;
|
|
this.type = type;
|
|
this.locale = locale;
|
|
this.strict = strict;
|
|
this.places = places;
|
|
}
|
|
=====*/
|
|
dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
|
|
// summary:
|
|
// Builds the regular needed to parse a number
|
|
// description:
|
|
// Returns regular expression with positive and negative match, group
|
|
// and decimal separators
|
|
return dojo.number._parseInfo(options).regexp; // String
|
|
}
|
|
|
|
dojo.number._parseInfo = function(/*Object?*/options){
|
|
options = options || {};
|
|
var locale = dojo.i18n.normalizeLocale(options.locale);
|
|
var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
|
|
var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
|
|
//TODO: memoize?
|
|
var group = bundle.group;
|
|
var decimal = bundle.decimal;
|
|
var factor = 1;
|
|
|
|
if(pattern.indexOf('%') != -1){
|
|
factor /= 100;
|
|
}else if(pattern.indexOf('\u2030') != -1){
|
|
factor /= 1000; // per mille
|
|
}else{
|
|
var isCurrency = pattern.indexOf('\u00a4') != -1;
|
|
if(isCurrency){
|
|
group = bundle.currencyGroup || group;
|
|
decimal = bundle.currencyDecimal || decimal;
|
|
}
|
|
}
|
|
|
|
//TODO: handle quoted escapes
|
|
var patternList = pattern.split(';');
|
|
if(patternList.length == 1){
|
|
patternList.push("-" + patternList[0]);
|
|
}
|
|
|
|
var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
|
|
pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
|
|
return pattern.replace(dojo.number._numberPatternRE, function(format){
|
|
var flags = {
|
|
signed: false,
|
|
separator: options.strict ? group : [group,""],
|
|
fractional: options.fractional,
|
|
decimal: decimal,
|
|
exponent: false};
|
|
var parts = format.split('.');
|
|
var places = options.places;
|
|
if(parts.length == 1 || places === 0){flags.fractional = false;}
|
|
else{
|
|
if(places === undefined){ places = parts[1].lastIndexOf('0')+1; }
|
|
if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
|
|
if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
|
|
flags.places = places;
|
|
}
|
|
var groups = parts[0].split(',');
|
|
if(groups.length>1){
|
|
flags.groupSize = groups.pop().length;
|
|
if(groups.length>1){
|
|
flags.groupSize2 = groups.pop().length;
|
|
}
|
|
}
|
|
return "("+dojo.number._realNumberRegexp(flags)+")";
|
|
});
|
|
}, true);
|
|
|
|
if(isCurrency){
|
|
// substitute the currency symbol for the placeholder in the pattern
|
|
re = re.replace(/(\s*)(\u00a4{1,3})(\s*)/g, function(match, before, target, after){
|
|
var prop = ["symbol", "currency", "displayName"][target.length-1];
|
|
var symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
|
|
before = before ? "\\s" : "";
|
|
after = after ? "\\s" : "";
|
|
if(!options.strict){
|
|
if(before){before += "*";}
|
|
if(after){after += "*";}
|
|
return "(?:"+before+symbol+after+")?";
|
|
}
|
|
return before+symbol+after;
|
|
});
|
|
}
|
|
|
|
//TODO: substitute localized sign/percent/permille/etc.?
|
|
|
|
// normalize whitespace and return
|
|
return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
|
|
}
|
|
|
|
/*=====
|
|
dojo.number.__ParseOptions = function(){
|
|
// pattern: String
|
|
// override pattern with this string. Default is provided based on
|
|
// locale.
|
|
// type: String?
|
|
// choose a format type based on the locale from the following:
|
|
// decimal, scientific, percent, currency. decimal by default.
|
|
// locale: String
|
|
// override the locale used to determine formatting rules
|
|
// strict: Boolean?
|
|
// strict parsing, false by default
|
|
// currency: Object
|
|
// object with currency information
|
|
this.pattern = pattern;
|
|
this.type = type;
|
|
this.locale = locale;
|
|
this.strict = strict;
|
|
this.currency = currency;
|
|
}
|
|
=====*/
|
|
dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
|
|
// summary:
|
|
// Convert a properly formatted string to a primitive Number, using
|
|
// locale-specific settings.
|
|
// description:
|
|
// Create a Number from a string using a known localized pattern.
|
|
// Formatting patterns are chosen appropriate to the locale
|
|
// and follow the syntax described by
|
|
// [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
|
|
// expression:
|
|
// A string representation of a Number
|
|
var info = dojo.number._parseInfo(options);
|
|
var results = (new RegExp("^"+info.regexp+"$")).exec(expression);
|
|
if(!results){
|
|
return NaN; //NaN
|
|
}
|
|
var absoluteMatch = results[1]; // match for the positive expression
|
|
if(!results[1]){
|
|
if(!results[2]){
|
|
return NaN; //NaN
|
|
}
|
|
// matched the negative pattern
|
|
absoluteMatch =results[2];
|
|
info.factor *= -1;
|
|
}
|
|
|
|
// Transform it to something Javascript can parse as a number. Normalize
|
|
// decimal point and strip out group separators or alternate forms of whitespace
|
|
absoluteMatch = absoluteMatch.
|
|
replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
|
|
replace(info.decimal, ".");
|
|
// Adjust for negative sign, percent, etc. as necessary
|
|
return Number(absoluteMatch) * info.factor; //Number
|
|
};
|
|
|
|
/*=====
|
|
dojo.number.__RealNumberRegexpFlags = function(){
|
|
// places: Number?
|
|
// The integer number of decimal places or a range given as "n,m". If
|
|
// not given, the decimal part is optional and the number of places is
|
|
// unlimited.
|
|
// decimal: String?
|
|
// A string for the character used as the decimal point. Default
|
|
// is ".".
|
|
// fractional: Boolean|Array?
|
|
// Whether decimal places are allowed. Can be true, false, or [true,
|
|
// false]. Default is [true, false]
|
|
// exponent: Boolean|Array?
|
|
// Express in exponential notation. Can be true, false, or [true,
|
|
// false]. Default is [true, false], (i.e. will match if the
|
|
// exponential part is present are not).
|
|
// eSigned: Boolean|Array?
|
|
// The leading plus-or-minus sign on the exponent. Can be true,
|
|
// false, or [true, false]. Default is [true, false], (i.e. will
|
|
// match if it is signed or unsigned). flags in regexp.integer can be
|
|
// applied.
|
|
this.places = places;
|
|
this.decimal = decimal;
|
|
this.fractional = fractional;
|
|
this.exponent = exponent;
|
|
this.eSigned = eSigned;
|
|
}
|
|
=====*/
|
|
|
|
dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
|
|
// summary:
|
|
// Builds a regular expression to match a real number in exponential
|
|
// notation
|
|
|
|
// assign default values to missing paramters
|
|
flags = flags || {};
|
|
//TODO: use mixin instead?
|
|
if(!("places" in flags)){ flags.places = Infinity; }
|
|
if(typeof flags.decimal != "string"){ flags.decimal = "."; }
|
|
if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
|
|
if(!("exponent" in flags)){ flags.exponent = [true, false]; }
|
|
if(!("eSigned" in flags)){ flags.eSigned = [true, false]; }
|
|
|
|
// integer RE
|
|
var integerRE = dojo.number._integerRegexp(flags);
|
|
|
|
// decimal RE
|
|
var decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
|
|
function(q){
|
|
var re = "";
|
|
if(q && (flags.places!==0)){
|
|
re = "\\" + flags.decimal;
|
|
if(flags.places == Infinity){
|
|
re = "(?:" + re + "\\d+)?";
|
|
}else{
|
|
re += "\\d{" + flags.places + "}";
|
|
}
|
|
}
|
|
return re;
|
|
},
|
|
true
|
|
);
|
|
|
|
// exponent RE
|
|
var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
|
|
function(q){
|
|
if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
|
|
return "";
|
|
}
|
|
);
|
|
|
|
// real number RE
|
|
var realRE = integerRE + decimalRE;
|
|
// allow for decimals without integers, e.g. .25
|
|
if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
|
|
return realRE + exponentRE; // String
|
|
};
|
|
|
|
/*=====
|
|
dojo.number.__IntegerRegexpFlags = function(){
|
|
// signed: Boolean?
|
|
// The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
|
|
// Default is `[true, false]`, (i.e. will match if it is signed
|
|
// or unsigned).
|
|
// separator: String?
|
|
// The character used as the thousands separator. Default is no
|
|
// separator. For more than one symbol use an array, e.g. `[",", ""]`,
|
|
// makes ',' optional.
|
|
// groupSize: Number?
|
|
// group size between separators
|
|
// groupSize2: Number?
|
|
// second grouping, where separators 2..n have a different interval than the first separator (for India)
|
|
this.signed = signed;
|
|
this.separator = separator;
|
|
this.groupSize = groupSize;
|
|
this.groupSize2 = groupSize2;
|
|
}
|
|
=====*/
|
|
|
|
dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
|
|
// summary:
|
|
// Builds a regular expression that matches an integer
|
|
|
|
// assign default values to missing paramters
|
|
flags = flags || {};
|
|
if(!("signed" in flags)){ flags.signed = [true, false]; }
|
|
if(!("separator" in flags)){
|
|
flags.separator = "";
|
|
}else if(!("groupSize" in flags)){
|
|
flags.groupSize = 3;
|
|
}
|
|
// build sign RE
|
|
var signRE = dojo.regexp.buildGroupRE(flags.signed,
|
|
function(q) { return q ? "[-+]" : ""; },
|
|
true
|
|
);
|
|
|
|
// number RE
|
|
var numberRE = dojo.regexp.buildGroupRE(flags.separator,
|
|
function(sep){
|
|
if(!sep){
|
|
return "(?:0|[1-9]\\d*)";
|
|
}
|
|
|
|
sep = dojo.regexp.escapeString(sep);
|
|
if(sep == " "){ sep = "\\s"; }
|
|
else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
|
|
|
|
var grp = flags.groupSize, grp2 = flags.groupSize2;
|
|
if(grp2){
|
|
var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
|
|
return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
|
|
}
|
|
return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
|
|
},
|
|
true
|
|
);
|
|
|
|
// integer RE
|
|
return signRE + numberRE; // String
|
|
}
|
|
|
|
}
|