123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /*
- Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- 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, "ROOT,af,af-na,ak,am,ar,ar-qa,ar-sa,ar-sy,ar-tn,ar-ye,as,asa,az,az-cyrl,be,bem,bez,bg,bm,bn,bo,brx,bs,ca,cgg,chr,cs,da,dav,de,de-at,de-ch,de-li,ebu,ee,el,el-cy,en,en-au,en-be,en-bw,en-bz,en-gb,en-ie,en-in,en-jm,en-mt,en-na,en-nz,en-tt,en-us-posix,en-za,en-zw,eo,es,es-419,es-cl,es-do,es-ec,es-gt,es-hn,es-mx,es-ni,es-pa,es-pe,es-pr,es-py,es-sv,es-us,es-uy,es-ve,et,eu,fa,fa-af,ff,fi,fil,fo,fr,fr-be,fr-ca,fr-ch,fr-lu,ga,gl,gsw,gu,guz,gv,ha,haw,he,hi,hr,hu,hy,id,ig,ii,in,is,it,it-ch,iw,ja,jmc,ka,kab,kam,kde,kea,khq,ki,kk,kl,kln,km,kn,ko,kok,ksb,kw,lg,lt,luo,luy,lv,mas,mer,mfe,mg,mk,ml,mo,mr,ms,ms-bn,mt,my,naq,nb,nd,ne,nl,nl-be,nn,no,nr,nso,nyn,om,or,pa,pl,ps,pt,pt-pt,rm,ro,rof,ru,rw,rwk,saq,seh,ses,sg,sh,shi,shi-tfng,si,sk,sl,sn,so,sq,sr,sr-latn-me,sr-me,ss,st,sv,sw,sw-ke,ta,te,teo,th,ti,tl,tn,tr,ts,tzm,uk,ur,ur-in,uz-af,uz-arab,ve,vi,vun,xh,xog,yo,zh,zh-hant,zh-hant-hk,zh-hk,zu");
- dojo.require("dojo.string");
- dojo.require("dojo.regexp");
- dojo.getObject("number", true, dojo);
- /*=====
- 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. Default value is based on locale. Overriding this property will defeat
- // localization. Literal characters in patterns are not supported.
- // type: String?
- // choose a format type based on the locale from the following:
- // decimal, scientific (not yet supported), 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 do not round.
- // locale: String?
- // override the locale used to determine formatting rules
- // fractional: Boolean?
- // If false, show no decimal places, overriding places and pattern settings.
- this.pattern = pattern;
- this.type = type;
- this.places = places;
- this.round = round;
- this.locale = locale;
- this.fractional = fractional;
- }
- =====*/
- 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
- // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
- // delimiters.
- // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
- // value:
- // the number to be formatted
- options = dojo.mixin({}, options || {});
- var locale = dojo.i18n.normalizeLocale(options.locale),
- bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
- options.customs = bundle;
- var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
- if(isNaN(value) || Math.abs(value) == Infinity){ 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,
- decimal = options.customs.decimal,
- patternList = pattern.split(';'),
- 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);
- }
- if(options.fractional === false){ options.places = 0; }
- return pattern.replace(numberPatternRE,
- dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
- };
- dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
- // summary:
- // Rounds to the nearest value with the given number of decimal places, away from zero
- // description:
- // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
- // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
- // fractional increments also, such as the nearest quarter.
- // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround.
- // value:
- // The number to round
- // places:
- // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
- // Must be non-negative.
- // increment:
- // Rounds next place to nearest value of increment/10. 10 by default.
- // example:
- // >>> dojo.number.round(-0.5)
- // -1
- // >>> dojo.number.round(162.295, 2)
- // 162.29 // note floating point error. Should be 162.3
- // >>> dojo.number.round(10.71, 0, 2.5)
- // 10.75
- var factor = 10 / (increment || 10);
- return (factor * +value).toFixed(places) / factor; // Number
- };
- if((0.9).toFixed() == 0){
- // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
- // is just after the rounding place and is >=5
- (function(){
- var round = dojo.number.round;
- dojo.number.round = function(v, p, m){
- var d = Math.pow(10, -p || 0), a = Math.abs(v);
- if(!v || a >= d || a * Math.pow(10, p + 1) < 5){
- d = 0;
- }
- return round(v, p, m) + (v > 0 ? d : -d);
- };
- })();
- }
- /*=====
- dojo.number.__FormatAbsoluteOptions = function(){
- // decimal: String?
- // the decimal separator
- // group: String?
- // the group separator
- // places: Number?|String?
- // number of decimal places. the range "n,m" will format to m 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("."),
- comma = typeof options.places == "string" && options.places.indexOf(","),
- maxPlaces = options.places;
- if(comma){
- maxPlaces = options.places.substring(comma + 1);
- }else if(!(maxPlaces >= 0)){
- maxPlaces = (patternParts[1] || []).length;
- }
- if(!(options.round < 0)){
- value = dojo.number.round(value, maxPlaces, options.round);
- }
- var valueParts = String(Math.abs(value)).split("."),
- fractional = valueParts[1] || "";
- if(patternParts[1] || options.places){
- if(comma){
- options.places = options.places.substring(0, comma);
- }
- // Pad fractional with trailing zeros
- var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1);
- if(pad > fractional.length){
- valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
- }
- // Truncate fractional
- if(maxPlaces < fractional.length){
- valueParts[1] = fractional.substr(0, maxPlaces);
- }
- }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(','),
- 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 [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
- // with this string. Default value is based on locale. Overriding this property will defeat
- // localization.
- // type: String?
- // choose a format type based on the locale from the following:
- // decimal, scientific (not yet supported), percent, currency. decimal by default.
- // locale: String?
- // override the locale used to determine formatting rules
- // strict: Boolean?
- // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
- // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
- // places: Number|String?
- // number of decimal places to accept: Infinity, a positive number, or
- // a range "n,m". Defined by pattern or Infinity if pattern not provided.
- 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),
- bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale),
- pattern = options.pattern || bundle[(options.type || "decimal") + "Format"],
- //TODO: memoize?
- group = bundle.group,
- decimal = bundle.decimal,
- 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
- },
- parts = format.split('.'),
- places = options.places;
- // special condition for percent (factor != 1)
- // allow decimal places even if not specified in pattern
- if(parts.length == 1 && factor != 1){
- parts[1] = "###";
- }
- if(parts.length == 1 || places === 0){
- flags.fractional = false;
- }else{
- if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; }
- 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\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){
- var prop = ["symbol", "currency", "displayName"][target.length-1],
- symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
- before = before ? "[\\s\\xa0]" : "";
- after = after ? "[\\s\\xa0]" : "";
- 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 [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
- // with this string. Default value is based on locale. Overriding this property will defeat
- // localization. Literal characters in patterns are not supported.
- // type: String?
- // choose a format type based on the locale from the following:
- // decimal, scientific (not yet supported), percent, currency. decimal by default.
- // locale: String?
- // override the locale used to determine formatting rules
- // strict: Boolean?
- // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
- // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
- // fractional: Boolean?|Array?
- // Whether to include the fractional portion, where the number of decimal places are implied by pattern
- // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
- this.pattern = pattern;
- this.type = type;
- this.locale = locale;
- this.strict = strict;
- this.fractional = fractional;
- }
- =====*/
- 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)
- // Note that literal characters in patterns are not supported.
- // expression:
- // A string representation of a Number
- var info = dojo.number._parseInfo(options),
- 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 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 used. Can be true, false, or [true,
- // false]. Default is [true, false] which means optional.
- // 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 parameters
- 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]; }
- var integerRE = dojo.number._integerRegexp(flags),
- 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
- );
- var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
- function(q){
- if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
- return "";
- }
- );
- 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 parameters
- flags = flags || {};
- if(!("signed" in flags)){ flags.signed = [true, false]; }
- if(!("separator" in flags)){
- flags.separator = "";
- }else if(!("groupSize" in flags)){
- flags.groupSize = 3;
- }
- var signRE = dojo.regexp.buildGroupRE(flags.signed,
- function(q){ return q ? "[-+]" : ""; },
- true
- );
- var numberRE = dojo.regexp.buildGroupRE(flags.separator,
- function(sep){
- if(!sep){
- return "(?:\\d+)";
- }
- sep = dojo.regexp.escapeString(sep);
- if(sep == " "){ sep = "\\s"; }
- else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
- var grp = flags.groupSize, grp2 = flags.groupSize2;
- //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
- 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
- );
- return signRE + numberRE; // String
- };
- }
|