diff options
Diffstat (limited to 'includes/js/dojo/number.js')
-rw-r--r-- | includes/js/dojo/number.js | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/includes/js/dojo/number.js b/includes/js/dojo/number.js new file mode 100644 index 0000000..4632acc --- /dev/null +++ b/includes/js/dojo/number.js @@ -0,0 +1,551 @@ +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 +} + +} |